Switch to golangci-lint
This commit vendors golangci-lint and enables all linters which do not complain right now. CI and scripting has been adapted as well. Signed-off-by: Sascha Grunert <sgrunert@suse.com> Closes: #1706 Approved by: rhatdan
This commit is contained in:
parent
ed56830e21
commit
e160a632b7
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
run:
|
||||
build-tags:
|
||||
- apparmor
|
||||
- ostree
|
||||
- seccomp
|
||||
- selinux
|
||||
concurrency: 6
|
||||
deadline: 5m
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- deadcode
|
||||
- depguard
|
||||
- dupl
|
||||
- gofmt
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- nakedret
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unused
|
||||
- varcheck
|
||||
# - bodyclose
|
||||
# - errcheck
|
||||
# - gochecknoglobals
|
||||
# - gochecknoinits
|
||||
# - goconst
|
||||
# - gocritic
|
||||
# - gocyclo
|
||||
# - goimports
|
||||
# - golint
|
||||
# - gosec
|
||||
# - interfacer
|
||||
# - lll
|
||||
# - maligned
|
||||
# - misspell
|
||||
# - prealloc
|
||||
# - scopelint
|
||||
# - stylecheck
|
||||
# - unconvert
|
||||
# - unparam
|
13
.travis.yml
13
.travis.yml
|
@ -45,11 +45,20 @@ before_install:
|
|||
- sudo apt-get -qq install software-properties-common
|
||||
- sudo add-apt-repository -y ppa:duggan/bats
|
||||
- sudo apt-get update
|
||||
- sudo apt-get -qq install bats btrfs-tools git libapparmor-dev libc-dev libdevmapper-dev libglib2.0-dev libgpgme11-dev libselinux1-dev linux-libc-dev realpath
|
||||
- sudo apt-get -qq install bats btrfs-tools git libapparmor-dev libc-dev libdevmapper-dev libglib2.0-dev libgpgme11-dev libselinux1-dev linux-libc-dev realpath e2fslibs-dev libfuse-dev
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce
|
||||
- mkdir /home/travis/auth
|
||||
- sudo mkdir -p /var/lib/containers/storage/overlay
|
||||
- >
|
||||
OSTREE_VERSION=v2019.2;
|
||||
git clone https://github.com/ostreedev/ostree &&
|
||||
pushd ostree &&
|
||||
git checkout $OSTREE_VERSION &&
|
||||
./autogen.sh --prefix=/usr &&
|
||||
sudo make -j4 install &&
|
||||
popd &&
|
||||
sudo rm -rf ostree
|
||||
install:
|
||||
# Let's create a self signed certificate and get it in the right places
|
||||
- hostname
|
||||
|
@ -86,7 +95,7 @@ script:
|
|||
- docker images
|
||||
- docker rmi localhost:5000/my-alpine
|
||||
# Setting up Docker Registry is complete, let's do Buildah testing!
|
||||
- make install.tools install.libseccomp.sudo all runc validate SECURITYTAGS="apparmor seccomp"
|
||||
- make install.tools install.libseccomp.sudo all runc validate lint SECURITYTAGS="apparmor seccomp"
|
||||
- go test -c -tags "apparmor seccomp `./btrfs_tag.sh` `./libdm_tag.sh` `./ostree_tag.sh` `./selinux_tag.sh`" ./cmd/buildah
|
||||
- tmp=`mktemp -d`; mkdir $tmp/root $tmp/runroot; sudo PATH="$PATH" ./buildah.test -test.v --root $tmp/root --runroot $tmp/runroot --storage-driver vfs --signature-policy `pwd`/tests/policy.json --registries-conf `pwd`/tests/registries.conf
|
||||
- cd tests; sudo PATH="$PATH" ./test_runner.sh
|
||||
|
|
9
Makefile
9
Makefile
|
@ -72,14 +72,9 @@ endif
|
|||
@./tests/validate/whitespace.sh
|
||||
@./tests/validate/govet.sh
|
||||
@./tests/validate/git-validation.sh
|
||||
@./tests/validate/gometalinter.sh . cmd/buildah
|
||||
|
||||
.PHONY: install.tools
|
||||
install.tools:
|
||||
env GO111MODULE=off \
|
||||
$(GO) get -u gopkg.in/alecthomas/gometalinter.v1
|
||||
env GO111MODULE=off \
|
||||
$(GOPATH)/bin/gometalinter.v1 -i
|
||||
make build -C tests/tools
|
||||
|
||||
.PHONY: runc
|
||||
|
@ -141,3 +136,7 @@ vendor:
|
|||
$(GO) mod tidy && \
|
||||
$(GO) mod vendor && \
|
||||
$(GO) mod verify
|
||||
|
||||
.PHONY: lint
|
||||
lint: install.tools
|
||||
./tests/tools/build/golangci-lint run
|
||||
|
|
|
@ -13,3 +13,4 @@ build:
|
|||
$(GO) build -o $(BUILDDIR)/go-md2man ./vendor/github.com/cpuguy83/go-md2man
|
||||
$(GO) build -o $(BUILDDIR)/git-validation ./vendor/github.com/vbatts/git-validation
|
||||
$(GO) build -o $(BUILDDIR)/ginkgo ./vendor/github.com/onsi/ginkgo/ginkgo
|
||||
$(GO) build -o $(BUILDDIR)/golangci-lint ./vendor/github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
|
|
|
@ -5,10 +5,8 @@ go 1.12
|
|||
require (
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
|
||||
github.com/cpuguy83/go-md2man v1.0.10
|
||||
github.com/golangci/golangci-lint v1.17.1
|
||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf // indirect
|
||||
github.com/hpcloud/tail v1.0.0 // indirect
|
||||
github.com/onsi/ginkgo v1.8.0
|
||||
github.com/vbatts/git-validation v1.0.0
|
||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
)
|
||||
|
|
|
@ -7,6 +7,7 @@ package tools
|
|||
|
||||
import (
|
||||
_ "github.com/cpuguy83/go-md2man"
|
||||
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
|
||||
_ "github.com/onsi/ginkgo/ginkgo"
|
||||
_ "github.com/vbatts/git-validation"
|
||||
)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
TAGS
|
||||
tags
|
||||
.*.swp
|
||||
tomlcheck/tomlcheck
|
||||
toml.test
|
|
@ -0,0 +1,15 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- tip
|
||||
install:
|
||||
- go install ./...
|
||||
- go get github.com/BurntSushi/toml-test
|
||||
script:
|
||||
- export PATH="$PATH:$HOME/gopath/bin"
|
||||
- make test
|
|
@ -0,0 +1,3 @@
|
|||
Compatible with TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 TOML authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,19 @@
|
|||
install:
|
||||
go install ./...
|
||||
|
||||
test: install
|
||||
go test -v
|
||||
toml-test toml-test-decoder
|
||||
toml-test -encoder toml-test-encoder
|
||||
|
||||
fmt:
|
||||
gofmt -w *.go */*.go
|
||||
colcheck *.go */*.go
|
||||
|
||||
tags:
|
||||
find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS
|
||||
|
||||
push:
|
||||
git push origin master
|
||||
git push github master
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
## TOML parser and encoder for Go with reflection
|
||||
|
||||
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
|
||||
reflection interface similar to Go's standard library `json` and `xml`
|
||||
packages. This package also supports the `encoding.TextUnmarshaler` and
|
||||
`encoding.TextMarshaler` interfaces so that you can define custom data
|
||||
representations. (There is an example of this below.)
|
||||
|
||||
Spec: https://github.com/toml-lang/toml
|
||||
|
||||
Compatible with TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
||||
|
||||
Documentation: https://godoc.org/github.com/BurntSushi/toml
|
||||
|
||||
Installation:
|
||||
|
||||
```bash
|
||||
go get github.com/BurntSushi/toml
|
||||
```
|
||||
|
||||
Try the toml validator:
|
||||
|
||||
```bash
|
||||
go get github.com/BurntSushi/toml/cmd/tomlv
|
||||
tomlv some-toml-file.toml
|
||||
```
|
||||
|
||||
[](https://travis-ci.org/BurntSushi/toml) [](https://godoc.org/github.com/BurntSushi/toml)
|
||||
|
||||
### Testing
|
||||
|
||||
This package passes all tests in
|
||||
[toml-test](https://github.com/BurntSushi/toml-test) for both the decoder
|
||||
and the encoder.
|
||||
|
||||
### Examples
|
||||
|
||||
This package works similarly to how the Go standard library handles `XML`
|
||||
and `JSON`. Namely, data is loaded into Go values via reflection.
|
||||
|
||||
For the simplest example, consider some TOML file as just a list of keys
|
||||
and values:
|
||||
|
||||
```toml
|
||||
Age = 25
|
||||
Cats = [ "Cauchy", "Plato" ]
|
||||
Pi = 3.14
|
||||
Perfection = [ 6, 28, 496, 8128 ]
|
||||
DOB = 1987-07-05T05:45:00Z
|
||||
```
|
||||
|
||||
Which could be defined in Go as:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Age int
|
||||
Cats []string
|
||||
Pi float64
|
||||
Perfection []int
|
||||
DOB time.Time // requires `import time`
|
||||
}
|
||||
```
|
||||
|
||||
And then decoded with:
|
||||
|
||||
```go
|
||||
var conf Config
|
||||
if _, err := toml.Decode(tomlData, &conf); err != nil {
|
||||
// handle error
|
||||
}
|
||||
```
|
||||
|
||||
You can also use struct tags if your struct field name doesn't map to a TOML
|
||||
key value directly:
|
||||
|
||||
```toml
|
||||
some_key_NAME = "wat"
|
||||
```
|
||||
|
||||
```go
|
||||
type TOML struct {
|
||||
ObscureKey string `toml:"some_key_NAME"`
|
||||
}
|
||||
```
|
||||
|
||||
### Using the `encoding.TextUnmarshaler` interface
|
||||
|
||||
Here's an example that automatically parses duration strings into
|
||||
`time.Duration` values:
|
||||
|
||||
```toml
|
||||
[[song]]
|
||||
name = "Thunder Road"
|
||||
duration = "4m49s"
|
||||
|
||||
[[song]]
|
||||
name = "Stairway to Heaven"
|
||||
duration = "8m03s"
|
||||
```
|
||||
|
||||
Which can be decoded with:
|
||||
|
||||
```go
|
||||
type song struct {
|
||||
Name string
|
||||
Duration duration
|
||||
}
|
||||
type songs struct {
|
||||
Song []song
|
||||
}
|
||||
var favorites songs
|
||||
if _, err := toml.Decode(blob, &favorites); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, s := range favorites.Song {
|
||||
fmt.Printf("%s (%s)\n", s.Name, s.Duration)
|
||||
}
|
||||
```
|
||||
|
||||
And you'll also need a `duration` type that satisfies the
|
||||
`encoding.TextUnmarshaler` interface:
|
||||
|
||||
```go
|
||||
type duration struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
func (d *duration) UnmarshalText(text []byte) error {
|
||||
var err error
|
||||
d.Duration, err = time.ParseDuration(string(text))
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
### More complex usage
|
||||
|
||||
Here's an example of how to load the example from the official spec page:
|
||||
|
||||
```toml
|
||||
# This is a TOML document. Boom.
|
||||
|
||||
title = "TOML Example"
|
||||
|
||||
[owner]
|
||||
name = "Tom Preston-Werner"
|
||||
organization = "GitHub"
|
||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
||||
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
|
||||
|
||||
[database]
|
||||
server = "192.168.1.1"
|
||||
ports = [ 8001, 8001, 8002 ]
|
||||
connection_max = 5000
|
||||
enabled = true
|
||||
|
||||
[servers]
|
||||
|
||||
# You can indent as you please. Tabs or spaces. TOML don't care.
|
||||
[servers.alpha]
|
||||
ip = "10.0.0.1"
|
||||
dc = "eqdc10"
|
||||
|
||||
[servers.beta]
|
||||
ip = "10.0.0.2"
|
||||
dc = "eqdc10"
|
||||
|
||||
[clients]
|
||||
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
||||
|
||||
# Line breaks are OK when inside arrays
|
||||
hosts = [
|
||||
"alpha",
|
||||
"omega"
|
||||
]
|
||||
```
|
||||
|
||||
And the corresponding Go types are:
|
||||
|
||||
```go
|
||||
type tomlConfig struct {
|
||||
Title string
|
||||
Owner ownerInfo
|
||||
DB database `toml:"database"`
|
||||
Servers map[string]server
|
||||
Clients clients
|
||||
}
|
||||
|
||||
type ownerInfo struct {
|
||||
Name string
|
||||
Org string `toml:"organization"`
|
||||
Bio string
|
||||
DOB time.Time
|
||||
}
|
||||
|
||||
type database struct {
|
||||
Server string
|
||||
Ports []int
|
||||
ConnMax int `toml:"connection_max"`
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
type server struct {
|
||||
IP string
|
||||
DC string
|
||||
}
|
||||
|
||||
type clients struct {
|
||||
Data [][]interface{}
|
||||
Hosts []string
|
||||
}
|
||||
```
|
||||
|
||||
Note that a case insensitive match will be tried if an exact match can't be
|
||||
found.
|
||||
|
||||
A working example of the above can be found in `_examples/example.{go,toml}`.
|
|
@ -0,0 +1,509 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func e(format string, args ...interface{}) error {
|
||||
return fmt.Errorf("toml: "+format, args...)
|
||||
}
|
||||
|
||||
// Unmarshaler is the interface implemented by objects that can unmarshal a
|
||||
// TOML description of themselves.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalTOML(interface{}) error
|
||||
}
|
||||
|
||||
// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`.
|
||||
func Unmarshal(p []byte, v interface{}) error {
|
||||
_, err := Decode(string(p), v)
|
||||
return err
|
||||
}
|
||||
|
||||
// Primitive is a TOML value that hasn't been decoded into a Go value.
|
||||
// When using the various `Decode*` functions, the type `Primitive` may
|
||||
// be given to any value, and its decoding will be delayed.
|
||||
//
|
||||
// A `Primitive` value can be decoded using the `PrimitiveDecode` function.
|
||||
//
|
||||
// The underlying representation of a `Primitive` value is subject to change.
|
||||
// Do not rely on it.
|
||||
//
|
||||
// N.B. Primitive values are still parsed, so using them will only avoid
|
||||
// the overhead of reflection. They can be useful when you don't know the
|
||||
// exact type of TOML data until run time.
|
||||
type Primitive struct {
|
||||
undecoded interface{}
|
||||
context Key
|
||||
}
|
||||
|
||||
// DEPRECATED!
|
||||
//
|
||||
// Use MetaData.PrimitiveDecode instead.
|
||||
func PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||
md := MetaData{decoded: make(map[string]bool)}
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
}
|
||||
|
||||
// PrimitiveDecode is just like the other `Decode*` functions, except it
|
||||
// decodes a TOML value that has already been parsed. Valid primitive values
|
||||
// can *only* be obtained from values filled by the decoder functions,
|
||||
// including this method. (i.e., `v` may contain more `Primitive`
|
||||
// values.)
|
||||
//
|
||||
// Meta data for primitive values is included in the meta data returned by
|
||||
// the `Decode*` functions with one exception: keys returned by the Undecoded
|
||||
// method will only reflect keys that were decoded. Namely, any keys hidden
|
||||
// behind a Primitive will be considered undecoded. Executing this method will
|
||||
// update the undecoded keys in the meta data. (See the example.)
|
||||
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||
md.context = primValue.context
|
||||
defer func() { md.context = nil }()
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
}
|
||||
|
||||
// Decode will decode the contents of `data` in TOML format into a pointer
|
||||
// `v`.
|
||||
//
|
||||
// TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be
|
||||
// used interchangeably.)
|
||||
//
|
||||
// TOML arrays of tables correspond to either a slice of structs or a slice
|
||||
// of maps.
|
||||
//
|
||||
// TOML datetimes correspond to Go `time.Time` values.
|
||||
//
|
||||
// All other TOML types (float, string, int, bool and array) correspond
|
||||
// to the obvious Go types.
|
||||
//
|
||||
// An exception to the above rules is if a type implements the
|
||||
// encoding.TextUnmarshaler interface. In this case, any primitive TOML value
|
||||
// (floats, strings, integers, booleans and datetimes) will be converted to
|
||||
// a byte string and given to the value's UnmarshalText method. See the
|
||||
// Unmarshaler example for a demonstration with time duration strings.
|
||||
//
|
||||
// Key mapping
|
||||
//
|
||||
// TOML keys can map to either keys in a Go map or field names in a Go
|
||||
// struct. The special `toml` struct tag may be used to map TOML keys to
|
||||
// struct fields that don't match the key name exactly. (See the example.)
|
||||
// A case insensitive match to struct names will be tried if an exact match
|
||||
// can't be found.
|
||||
//
|
||||
// The mapping between TOML values and Go values is loose. That is, there
|
||||
// may exist TOML values that cannot be placed into your representation, and
|
||||
// there may be parts of your representation that do not correspond to
|
||||
// TOML values. This loose mapping can be made stricter by using the IsDefined
|
||||
// and/or Undecoded methods on the MetaData returned.
|
||||
//
|
||||
// This decoder will not handle cyclic types. If a cyclic type is passed,
|
||||
// `Decode` will not terminate.
|
||||
func Decode(data string, v interface{}) (MetaData, error) {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
|
||||
}
|
||||
if rv.IsNil() {
|
||||
return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
|
||||
}
|
||||
p, err := parse(data)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
md := MetaData{
|
||||
p.mapping, p.types, p.ordered,
|
||||
make(map[string]bool, len(p.ordered)), nil,
|
||||
}
|
||||
return md, md.unify(p.mapping, indirect(rv))
|
||||
}
|
||||
|
||||
// DecodeFile is just like Decode, except it will automatically read the
|
||||
// contents of the file at `fpath` and decode it for you.
|
||||
func DecodeFile(fpath string, v interface{}) (MetaData, error) {
|
||||
bs, err := ioutil.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
return Decode(string(bs), v)
|
||||
}
|
||||
|
||||
// DecodeReader is just like Decode, except it will consume all bytes
|
||||
// from the reader and decode it for you.
|
||||
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) {
|
||||
bs, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
return Decode(string(bs), v)
|
||||
}
|
||||
|
||||
// unify performs a sort of type unification based on the structure of `rv`,
|
||||
// which is the client representation.
|
||||
//
|
||||
// Any type mismatch produces an error. Finding a type that we don't know
|
||||
// how to handle produces an unsupported type error.
|
||||
func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
||||
|
||||
// Special case. Look for a `Primitive` value.
|
||||
if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() {
|
||||
// Save the undecoded data and the key context into the primitive
|
||||
// value.
|
||||
context := make(Key, len(md.context))
|
||||
copy(context, md.context)
|
||||
rv.Set(reflect.ValueOf(Primitive{
|
||||
undecoded: data,
|
||||
context: context,
|
||||
}))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Special case. Unmarshaler Interface support.
|
||||
if rv.CanAddr() {
|
||||
if v, ok := rv.Addr().Interface().(Unmarshaler); ok {
|
||||
return v.UnmarshalTOML(data)
|
||||
}
|
||||
}
|
||||
|
||||
// Special case. Handle time.Time values specifically.
|
||||
// TODO: Remove this code when we decide to drop support for Go 1.1.
|
||||
// This isn't necessary in Go 1.2 because time.Time satisfies the encoding
|
||||
// interfaces.
|
||||
if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) {
|
||||
return md.unifyDatetime(data, rv)
|
||||
}
|
||||
|
||||
// Special case. Look for a value satisfying the TextUnmarshaler interface.
|
||||
if v, ok := rv.Interface().(TextUnmarshaler); ok {
|
||||
return md.unifyText(data, v)
|
||||
}
|
||||
// BUG(burntsushi)
|
||||
// The behavior here is incorrect whenever a Go type satisfies the
|
||||
// encoding.TextUnmarshaler interface but also corresponds to a TOML
|
||||
// hash or array. In particular, the unmarshaler should only be applied
|
||||
// to primitive TOML values. But at this point, it will be applied to
|
||||
// all kinds of values and produce an incorrect error whenever those values
|
||||
// are hashes or arrays (including arrays of tables).
|
||||
|
||||
k := rv.Kind()
|
||||
|
||||
// laziness
|
||||
if k >= reflect.Int && k <= reflect.Uint64 {
|
||||
return md.unifyInt(data, rv)
|
||||
}
|
||||
switch k {
|
||||
case reflect.Ptr:
|
||||
elem := reflect.New(rv.Type().Elem())
|
||||
err := md.unify(data, reflect.Indirect(elem))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rv.Set(elem)
|
||||
return nil
|
||||
case reflect.Struct:
|
||||
return md.unifyStruct(data, rv)
|
||||
case reflect.Map:
|
||||
return md.unifyMap(data, rv)
|
||||
case reflect.Array:
|
||||
return md.unifyArray(data, rv)
|
||||
case reflect.Slice:
|
||||
return md.unifySlice(data, rv)
|
||||
case reflect.String:
|
||||
return md.unifyString(data, rv)
|
||||
case reflect.Bool:
|
||||
return md.unifyBool(data, rv)
|
||||
case reflect.Interface:
|
||||
// we only support empty interfaces.
|
||||
if rv.NumMethod() > 0 {
|
||||
return e("unsupported type %s", rv.Type())
|
||||
}
|
||||
return md.unifyAnything(data, rv)
|
||||
case reflect.Float32:
|
||||
fallthrough
|
||||
case reflect.Float64:
|
||||
return md.unifyFloat64(data, rv)
|
||||
}
|
||||
return e("unsupported type %s", rv.Kind())
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
||||
tmap, ok := mapping.(map[string]interface{})
|
||||
if !ok {
|
||||
if mapping == nil {
|
||||
return nil
|
||||
}
|
||||
return e("type mismatch for %s: expected table but found %T",
|
||||
rv.Type().String(), mapping)
|
||||
}
|
||||
|
||||
for key, datum := range tmap {
|
||||
var f *field
|
||||
fields := cachedTypeFields(rv.Type())
|
||||
for i := range fields {
|
||||
ff := &fields[i]
|
||||
if ff.name == key {
|
||||
f = ff
|
||||
break
|
||||
}
|
||||
if f == nil && strings.EqualFold(ff.name, key) {
|
||||
f = ff
|
||||
}
|
||||
}
|
||||
if f != nil {
|
||||
subv := rv
|
||||
for _, i := range f.index {
|
||||
subv = indirect(subv.Field(i))
|
||||
}
|
||||
if isUnifiable(subv) {
|
||||
md.decoded[md.context.add(key).String()] = true
|
||||
md.context = append(md.context, key)
|
||||
if err := md.unify(datum, subv); err != nil {
|
||||
return err
|
||||
}
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
} else if f.name != "" {
|
||||
// Bad user! No soup for you!
|
||||
return e("cannot write unexported field %s.%s",
|
||||
rv.Type().String(), f.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
||||
tmap, ok := mapping.(map[string]interface{})
|
||||
if !ok {
|
||||
if tmap == nil {
|
||||
return nil
|
||||
}
|
||||
return badtype("map", mapping)
|
||||
}
|
||||
if rv.IsNil() {
|
||||
rv.Set(reflect.MakeMap(rv.Type()))
|
||||
}
|
||||
for k, v := range tmap {
|
||||
md.decoded[md.context.add(k).String()] = true
|
||||
md.context = append(md.context, k)
|
||||
|
||||
rvkey := indirect(reflect.New(rv.Type().Key()))
|
||||
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
|
||||
if err := md.unify(v, rvval); err != nil {
|
||||
return err
|
||||
}
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
|
||||
rvkey.SetString(k)
|
||||
rv.SetMapIndex(rvkey, rvval)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
||||
datav := reflect.ValueOf(data)
|
||||
if datav.Kind() != reflect.Slice {
|
||||
if !datav.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return badtype("slice", data)
|
||||
}
|
||||
sliceLen := datav.Len()
|
||||
if sliceLen != rv.Len() {
|
||||
return e("expected array length %d; got TOML array of length %d",
|
||||
rv.Len(), sliceLen)
|
||||
}
|
||||
return md.unifySliceArray(datav, rv)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
||||
datav := reflect.ValueOf(data)
|
||||
if datav.Kind() != reflect.Slice {
|
||||
if !datav.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return badtype("slice", data)
|
||||
}
|
||||
n := datav.Len()
|
||||
if rv.IsNil() || rv.Cap() < n {
|
||||
rv.Set(reflect.MakeSlice(rv.Type(), n, n))
|
||||
}
|
||||
rv.SetLen(n)
|
||||
return md.unifySliceArray(datav, rv)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
|
||||
sliceLen := data.Len()
|
||||
for i := 0; i < sliceLen; i++ {
|
||||
v := data.Index(i).Interface()
|
||||
sliceval := indirect(rv.Index(i))
|
||||
if err := md.unify(v, sliceval); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error {
|
||||
if _, ok := data.(time.Time); ok {
|
||||
rv.Set(reflect.ValueOf(data))
|
||||
return nil
|
||||
}
|
||||
return badtype("time.Time", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
|
||||
if s, ok := data.(string); ok {
|
||||
rv.SetString(s)
|
||||
return nil
|
||||
}
|
||||
return badtype("string", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
|
||||
if num, ok := data.(float64); ok {
|
||||
switch rv.Kind() {
|
||||
case reflect.Float32:
|
||||
fallthrough
|
||||
case reflect.Float64:
|
||||
rv.SetFloat(num)
|
||||
default:
|
||||
panic("bug")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return badtype("float", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
|
||||
if num, ok := data.(int64); ok {
|
||||
if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 {
|
||||
switch rv.Kind() {
|
||||
case reflect.Int, reflect.Int64:
|
||||
// No bounds checking necessary.
|
||||
case reflect.Int8:
|
||||
if num < math.MinInt8 || num > math.MaxInt8 {
|
||||
return e("value %d is out of range for int8", num)
|
||||
}
|
||||
case reflect.Int16:
|
||||
if num < math.MinInt16 || num > math.MaxInt16 {
|
||||
return e("value %d is out of range for int16", num)
|
||||
}
|
||||
case reflect.Int32:
|
||||
if num < math.MinInt32 || num > math.MaxInt32 {
|
||||
return e("value %d is out of range for int32", num)
|
||||
}
|
||||
}
|
||||
rv.SetInt(num)
|
||||
} else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 {
|
||||
unum := uint64(num)
|
||||
switch rv.Kind() {
|
||||
case reflect.Uint, reflect.Uint64:
|
||||
// No bounds checking necessary.
|
||||
case reflect.Uint8:
|
||||
if num < 0 || unum > math.MaxUint8 {
|
||||
return e("value %d is out of range for uint8", num)
|
||||
}
|
||||
case reflect.Uint16:
|
||||
if num < 0 || unum > math.MaxUint16 {
|
||||
return e("value %d is out of range for uint16", num)
|
||||
}
|
||||
case reflect.Uint32:
|
||||
if num < 0 || unum > math.MaxUint32 {
|
||||
return e("value %d is out of range for uint32", num)
|
||||
}
|
||||
}
|
||||
rv.SetUint(unum)
|
||||
} else {
|
||||
panic("unreachable")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return badtype("integer", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
|
||||
if b, ok := data.(bool); ok {
|
||||
rv.SetBool(b)
|
||||
return nil
|
||||
}
|
||||
return badtype("boolean", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
|
||||
rv.Set(reflect.ValueOf(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
|
||||
var s string
|
||||
switch sdata := data.(type) {
|
||||
case TextMarshaler:
|
||||
text, err := sdata.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s = string(text)
|
||||
case fmt.Stringer:
|
||||
s = sdata.String()
|
||||
case string:
|
||||
s = sdata
|
||||
case bool:
|
||||
s = fmt.Sprintf("%v", sdata)
|
||||
case int64:
|
||||
s = fmt.Sprintf("%d", sdata)
|
||||
case float64:
|
||||
s = fmt.Sprintf("%f", sdata)
|
||||
default:
|
||||
return badtype("primitive (string-like)", data)
|
||||
}
|
||||
if err := v.UnmarshalText([]byte(s)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// rvalue returns a reflect.Value of `v`. All pointers are resolved.
|
||||
func rvalue(v interface{}) reflect.Value {
|
||||
return indirect(reflect.ValueOf(v))
|
||||
}
|
||||
|
||||
// indirect returns the value pointed to by a pointer.
|
||||
// Pointers are followed until the value is not a pointer.
|
||||
// New values are allocated for each nil pointer.
|
||||
//
|
||||
// An exception to this rule is if the value satisfies an interface of
|
||||
// interest to us (like encoding.TextUnmarshaler).
|
||||
func indirect(v reflect.Value) reflect.Value {
|
||||
if v.Kind() != reflect.Ptr {
|
||||
if v.CanSet() {
|
||||
pv := v.Addr()
|
||||
if _, ok := pv.Interface().(TextUnmarshaler); ok {
|
||||
return pv
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
return indirect(reflect.Indirect(v))
|
||||
}
|
||||
|
||||
func isUnifiable(rv reflect.Value) bool {
|
||||
if rv.CanSet() {
|
||||
return true
|
||||
}
|
||||
if _, ok := rv.Interface().(TextUnmarshaler); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func badtype(expected string, data interface{}) error {
|
||||
return e("cannot load TOML value of type %T into a Go %s", data, expected)
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package toml
|
||||
|
||||
import "strings"
|
||||
|
||||
// MetaData allows access to meta information about TOML data that may not
|
||||
// be inferrable via reflection. In particular, whether a key has been defined
|
||||
// and the TOML type of a key.
|
||||
type MetaData struct {
|
||||
mapping map[string]interface{}
|
||||
types map[string]tomlType
|
||||
keys []Key
|
||||
decoded map[string]bool
|
||||
context Key // Used only during decoding.
|
||||
}
|
||||
|
||||
// IsDefined returns true if the key given exists in the TOML data. The key
|
||||
// should be specified hierarchially. e.g.,
|
||||
//
|
||||
// // access the TOML key 'a.b.c'
|
||||
// IsDefined("a", "b", "c")
|
||||
//
|
||||
// IsDefined will return false if an empty key given. Keys are case sensitive.
|
||||
func (md *MetaData) IsDefined(key ...string) bool {
|
||||
if len(key) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var hash map[string]interface{}
|
||||
var ok bool
|
||||
var hashOrVal interface{} = md.mapping
|
||||
for _, k := range key {
|
||||
if hash, ok = hashOrVal.(map[string]interface{}); !ok {
|
||||
return false
|
||||
}
|
||||
if hashOrVal, ok = hash[k]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Type returns a string representation of the type of the key specified.
|
||||
//
|
||||
// Type will return the empty string if given an empty key or a key that
|
||||
// does not exist. Keys are case sensitive.
|
||||
func (md *MetaData) Type(key ...string) string {
|
||||
fullkey := strings.Join(key, ".")
|
||||
if typ, ok := md.types[fullkey]; ok {
|
||||
return typ.typeString()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Key is the type of any TOML key, including key groups. Use (MetaData).Keys
|
||||
// to get values of this type.
|
||||
type Key []string
|
||||
|
||||
func (k Key) String() string {
|
||||
return strings.Join(k, ".")
|
||||
}
|
||||
|
||||
func (k Key) maybeQuotedAll() string {
|
||||
var ss []string
|
||||
for i := range k {
|
||||
ss = append(ss, k.maybeQuoted(i))
|
||||
}
|
||||
return strings.Join(ss, ".")
|
||||
}
|
||||
|
||||
func (k Key) maybeQuoted(i int) string {
|
||||
quote := false
|
||||
for _, c := range k[i] {
|
||||
if !isBareKeyChar(c) {
|
||||
quote = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if quote {
|
||||
return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\""
|
||||
}
|
||||
return k[i]
|
||||
}
|
||||
|
||||
func (k Key) add(piece string) Key {
|
||||
newKey := make(Key, len(k)+1)
|
||||
copy(newKey, k)
|
||||
newKey[len(k)] = piece
|
||||
return newKey
|
||||
}
|
||||
|
||||
// Keys returns a slice of every key in the TOML data, including key groups.
|
||||
// Each key is itself a slice, where the first element is the top of the
|
||||
// hierarchy and the last is the most specific.
|
||||
//
|
||||
// The list will have the same order as the keys appeared in the TOML data.
|
||||
//
|
||||
// All keys returned are non-empty.
|
||||
func (md *MetaData) Keys() []Key {
|
||||
return md.keys
|
||||
}
|
||||
|
||||
// Undecoded returns all keys that have not been decoded in the order in which
|
||||
// they appear in the original TOML document.
|
||||
//
|
||||
// This includes keys that haven't been decoded because of a Primitive value.
|
||||
// Once the Primitive value is decoded, the keys will be considered decoded.
|
||||
//
|
||||
// Also note that decoding into an empty interface will result in no decoding,
|
||||
// and so no keys will be considered decoded.
|
||||
//
|
||||
// In this sense, the Undecoded keys correspond to keys in the TOML document
|
||||
// that do not have a concrete type in your representation.
|
||||
func (md *MetaData) Undecoded() []Key {
|
||||
undecoded := make([]Key, 0, len(md.keys))
|
||||
for _, key := range md.keys {
|
||||
if !md.decoded[key.String()] {
|
||||
undecoded = append(undecoded, key)
|
||||
}
|
||||
}
|
||||
return undecoded
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
Package toml provides facilities for decoding and encoding TOML configuration
|
||||
files via reflection. There is also support for delaying decoding with
|
||||
the Primitive type, and querying the set of keys in a TOML document with the
|
||||
MetaData type.
|
||||
|
||||
The specification implemented: https://github.com/toml-lang/toml
|
||||
|
||||
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify
|
||||
whether a file is a valid TOML document. It can also be used to print the
|
||||
type of each key in a TOML document.
|
||||
|
||||
Testing
|
||||
|
||||
There are two important types of tests used for this package. The first is
|
||||
contained inside '*_test.go' files and uses the standard Go unit testing
|
||||
framework. These tests are primarily devoted to holistically testing the
|
||||
decoder and encoder.
|
||||
|
||||
The second type of testing is used to verify the implementation's adherence
|
||||
to the TOML specification. These tests have been factored into their own
|
||||
project: https://github.com/BurntSushi/toml-test
|
||||
|
||||
The reason the tests are in a separate project is so that they can be used by
|
||||
any implementation of TOML. Namely, it is language agnostic.
|
||||
*/
|
||||
package toml
|
|
@ -0,0 +1,568 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type tomlEncodeError struct{ error }
|
||||
|
||||
var (
|
||||
errArrayMixedElementTypes = errors.New(
|
||||
"toml: cannot encode array with mixed element types")
|
||||
errArrayNilElement = errors.New(
|
||||
"toml: cannot encode array with nil element")
|
||||
errNonString = errors.New(
|
||||
"toml: cannot encode a map with non-string key type")
|
||||
errAnonNonStruct = errors.New(
|
||||
"toml: cannot encode an anonymous field that is not a struct")
|
||||
errArrayNoTable = errors.New(
|
||||
"toml: TOML array element cannot contain a table")
|
||||
errNoKey = errors.New(
|
||||
"toml: top-level values must be Go maps or structs")
|
||||
errAnything = errors.New("") // used in testing
|
||||
)
|
||||
|
||||
var quotedReplacer = strings.NewReplacer(
|
||||
"\t", "\\t",
|
||||
"\n", "\\n",
|
||||
"\r", "\\r",
|
||||
"\"", "\\\"",
|
||||
"\\", "\\\\",
|
||||
)
|
||||
|
||||
// Encoder controls the encoding of Go values to a TOML document to some
|
||||
// io.Writer.
|
||||
//
|
||||
// The indentation level can be controlled with the Indent field.
|
||||
type Encoder struct {
|
||||
// A single indentation level. By default it is two spaces.
|
||||
Indent string
|
||||
|
||||
// hasWritten is whether we have written any output to w yet.
|
||||
hasWritten bool
|
||||
w *bufio.Writer
|
||||
}
|
||||
|
||||
// NewEncoder returns a TOML encoder that encodes Go values to the io.Writer
|
||||
// given. By default, a single indentation level is 2 spaces.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{
|
||||
w: bufio.NewWriter(w),
|
||||
Indent: " ",
|
||||
}
|
||||
}
|
||||
|
||||
// Encode writes a TOML representation of the Go value to the underlying
|
||||
// io.Writer. If the value given cannot be encoded to a valid TOML document,
|
||||
// then an error is returned.
|
||||
//
|
||||
// The mapping between Go values and TOML values should be precisely the same
|
||||
// as for the Decode* functions. Similarly, the TextMarshaler interface is
|
||||
// supported by encoding the resulting bytes as strings. (If you want to write
|
||||
// arbitrary binary data then you will need to use something like base64 since
|
||||
// TOML does not have any binary types.)
|
||||
//
|
||||
// When encoding TOML hashes (i.e., Go maps or structs), keys without any
|
||||
// sub-hashes are encoded first.
|
||||
//
|
||||
// If a Go map is encoded, then its keys are sorted alphabetically for
|
||||
// deterministic output. More control over this behavior may be provided if
|
||||
// there is demand for it.
|
||||
//
|
||||
// Encoding Go values without a corresponding TOML representation---like map
|
||||
// types with non-string keys---will cause an error to be returned. Similarly
|
||||
// for mixed arrays/slices, arrays/slices with nil elements, embedded
|
||||
// non-struct types and nested slices containing maps or structs.
|
||||
// (e.g., [][]map[string]string is not allowed but []map[string]string is OK
|
||||
// and so is []map[string][]string.)
|
||||
func (enc *Encoder) Encode(v interface{}) error {
|
||||
rv := eindirect(reflect.ValueOf(v))
|
||||
if err := enc.safeEncode(Key([]string{}), rv); err != nil {
|
||||
return err
|
||||
}
|
||||
return enc.w.Flush()
|
||||
}
|
||||
|
||||
func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if terr, ok := r.(tomlEncodeError); ok {
|
||||
err = terr.error
|
||||
return
|
||||
}
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
enc.encode(key, rv)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (enc *Encoder) encode(key Key, rv reflect.Value) {
|
||||
// Special case. Time needs to be in ISO8601 format.
|
||||
// Special case. If we can marshal the type to text, then we used that.
|
||||
// Basically, this prevents the encoder for handling these types as
|
||||
// generic structs (or whatever the underlying type of a TextMarshaler is).
|
||||
switch rv.Interface().(type) {
|
||||
case time.Time, TextMarshaler:
|
||||
enc.keyEqElement(key, rv)
|
||||
return
|
||||
}
|
||||
|
||||
k := rv.Kind()
|
||||
switch k {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||
reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
|
||||
enc.keyEqElement(key, rv)
|
||||
case reflect.Array, reflect.Slice:
|
||||
if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
|
||||
enc.eArrayOfTables(key, rv)
|
||||
} else {
|
||||
enc.keyEqElement(key, rv)
|
||||
}
|
||||
case reflect.Interface:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.encode(key, rv.Elem())
|
||||
case reflect.Map:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.eTable(key, rv)
|
||||
case reflect.Ptr:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.encode(key, rv.Elem())
|
||||
case reflect.Struct:
|
||||
enc.eTable(key, rv)
|
||||
default:
|
||||
panic(e("unsupported type for key '%s': %s", key, k))
|
||||
}
|
||||
}
|
||||
|
||||
// eElement encodes any value that can be an array element (primitives and
|
||||
// arrays).
|
||||
func (enc *Encoder) eElement(rv reflect.Value) {
|
||||
switch v := rv.Interface().(type) {
|
||||
case time.Time:
|
||||
// Special case time.Time as a primitive. Has to come before
|
||||
// TextMarshaler below because time.Time implements
|
||||
// encoding.TextMarshaler, but we need to always use UTC.
|
||||
enc.wf(v.UTC().Format("2006-01-02T15:04:05Z"))
|
||||
return
|
||||
case TextMarshaler:
|
||||
// Special case. Use text marshaler if it's available for this value.
|
||||
if s, err := v.MarshalText(); err != nil {
|
||||
encPanic(err)
|
||||
} else {
|
||||
enc.writeQuoted(string(s))
|
||||
}
|
||||
return
|
||||
}
|
||||
switch rv.Kind() {
|
||||
case reflect.Bool:
|
||||
enc.wf(strconv.FormatBool(rv.Bool()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64:
|
||||
enc.wf(strconv.FormatInt(rv.Int(), 10))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16,
|
||||
reflect.Uint32, reflect.Uint64:
|
||||
enc.wf(strconv.FormatUint(rv.Uint(), 10))
|
||||
case reflect.Float32:
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32)))
|
||||
case reflect.Float64:
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64)))
|
||||
case reflect.Array, reflect.Slice:
|
||||
enc.eArrayOrSliceElement(rv)
|
||||
case reflect.Interface:
|
||||
enc.eElement(rv.Elem())
|
||||
case reflect.String:
|
||||
enc.writeQuoted(rv.String())
|
||||
default:
|
||||
panic(e("unexpected primitive type: %s", rv.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
// By the TOML spec, all floats must have a decimal with at least one
|
||||
// number on either side.
|
||||
func floatAddDecimal(fstr string) string {
|
||||
if !strings.Contains(fstr, ".") {
|
||||
return fstr + ".0"
|
||||
}
|
||||
return fstr
|
||||
}
|
||||
|
||||
func (enc *Encoder) writeQuoted(s string) {
|
||||
enc.wf("\"%s\"", quotedReplacer.Replace(s))
|
||||
}
|
||||
|
||||
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
|
||||
length := rv.Len()
|
||||
enc.wf("[")
|
||||
for i := 0; i < length; i++ {
|
||||
elem := rv.Index(i)
|
||||
enc.eElement(elem)
|
||||
if i != length-1 {
|
||||
enc.wf(", ")
|
||||
}
|
||||
}
|
||||
enc.wf("]")
|
||||
}
|
||||
|
||||
func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
|
||||
if len(key) == 0 {
|
||||
encPanic(errNoKey)
|
||||
}
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
trv := rv.Index(i)
|
||||
if isNil(trv) {
|
||||
continue
|
||||
}
|
||||
panicIfInvalidKey(key)
|
||||
enc.newline()
|
||||
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
|
||||
enc.newline()
|
||||
enc.eMapOrStruct(key, trv)
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
|
||||
panicIfInvalidKey(key)
|
||||
if len(key) == 1 {
|
||||
// Output an extra newline between top-level tables.
|
||||
// (The newline isn't written if nothing else has been written though.)
|
||||
enc.newline()
|
||||
}
|
||||
if len(key) > 0 {
|
||||
enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
|
||||
enc.newline()
|
||||
}
|
||||
enc.eMapOrStruct(key, rv)
|
||||
}
|
||||
|
||||
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) {
|
||||
switch rv := eindirect(rv); rv.Kind() {
|
||||
case reflect.Map:
|
||||
enc.eMap(key, rv)
|
||||
case reflect.Struct:
|
||||
enc.eStruct(key, rv)
|
||||
default:
|
||||
panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) eMap(key Key, rv reflect.Value) {
|
||||
rt := rv.Type()
|
||||
if rt.Key().Kind() != reflect.String {
|
||||
encPanic(errNonString)
|
||||
}
|
||||
|
||||
// Sort keys so that we have deterministic output. And write keys directly
|
||||
// underneath this key first, before writing sub-structs or sub-maps.
|
||||
var mapKeysDirect, mapKeysSub []string
|
||||
for _, mapKey := range rv.MapKeys() {
|
||||
k := mapKey.String()
|
||||
if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) {
|
||||
mapKeysSub = append(mapKeysSub, k)
|
||||
} else {
|
||||
mapKeysDirect = append(mapKeysDirect, k)
|
||||
}
|
||||
}
|
||||
|
||||
var writeMapKeys = func(mapKeys []string) {
|
||||
sort.Strings(mapKeys)
|
||||
for _, mapKey := range mapKeys {
|
||||
mrv := rv.MapIndex(reflect.ValueOf(mapKey))
|
||||
if isNil(mrv) {
|
||||
// Don't write anything for nil fields.
|
||||
continue
|
||||
}
|
||||
enc.encode(key.add(mapKey), mrv)
|
||||
}
|
||||
}
|
||||
writeMapKeys(mapKeysDirect)
|
||||
writeMapKeys(mapKeysSub)
|
||||
}
|
||||
|
||||
func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
|
||||
// Write keys for fields directly under this key first, because if we write
|
||||
// a field that creates a new table, then all keys under it will be in that
|
||||
// table (not the one we're writing here).
|
||||
rt := rv.Type()
|
||||
var fieldsDirect, fieldsSub [][]int
|
||||
var addFields func(rt reflect.Type, rv reflect.Value, start []int)
|
||||
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
f := rt.Field(i)
|
||||
// skip unexported fields
|
||||
if f.PkgPath != "" && !f.Anonymous {
|
||||
continue
|
||||
}
|
||||
frv := rv.Field(i)
|
||||
if f.Anonymous {
|
||||
t := f.Type
|
||||
switch t.Kind() {
|
||||
case reflect.Struct:
|
||||
// Treat anonymous struct fields with
|
||||
// tag names as though they are not
|
||||
// anonymous, like encoding/json does.
|
||||
if getOptions(f.Tag).name == "" {
|
||||
addFields(t, frv, f.Index)
|
||||
continue
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if t.Elem().Kind() == reflect.Struct &&
|
||||
getOptions(f.Tag).name == "" {
|
||||
if !frv.IsNil() {
|
||||
addFields(t.Elem(), frv.Elem(), f.Index)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Fall through to the normal field encoding logic below
|
||||
// for non-struct anonymous fields.
|
||||
}
|
||||
}
|
||||
|
||||
if typeIsHash(tomlTypeOfGo(frv)) {
|
||||
fieldsSub = append(fieldsSub, append(start, f.Index...))
|
||||
} else {
|
||||
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
|
||||
}
|
||||
}
|
||||
}
|
||||
addFields(rt, rv, nil)
|
||||
|
||||
var writeFields = func(fields [][]int) {
|
||||
for _, fieldIndex := range fields {
|
||||
sft := rt.FieldByIndex(fieldIndex)
|
||||
sf := rv.FieldByIndex(fieldIndex)
|
||||
if isNil(sf) {
|
||||
// Don't write anything for nil fields.
|
||||
continue
|
||||
}
|
||||
|
||||
opts := getOptions(sft.Tag)
|
||||
if opts.skip {
|
||||
continue
|
||||
}
|
||||
keyName := sft.Name
|
||||
if opts.name != "" {
|
||||
keyName = opts.name
|
||||
}
|
||||
if opts.omitempty && isEmpty(sf) {
|
||||
continue
|
||||
}
|
||||
if opts.omitzero && isZero(sf) {
|
||||
continue
|
||||
}
|
||||
|
||||
enc.encode(key.add(keyName), sf)
|
||||
}
|
||||
}
|
||||
writeFields(fieldsDirect)
|
||||
writeFields(fieldsSub)
|
||||
}
|
||||
|
||||
// tomlTypeName returns the TOML type name of the Go value's type. It is
|
||||
// used to determine whether the types of array elements are mixed (which is
|
||||
// forbidden). If the Go value is nil, then it is illegal for it to be an array
|
||||
// element, and valueIsNil is returned as true.
|
||||
|
||||
// Returns the TOML type of a Go value. The type may be `nil`, which means
|
||||
// no concrete TOML type could be found.
|
||||
func tomlTypeOfGo(rv reflect.Value) tomlType {
|
||||
if isNil(rv) || !rv.IsValid() {
|
||||
return nil
|
||||
}
|
||||
switch rv.Kind() {
|
||||
case reflect.Bool:
|
||||
return tomlBool
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||
reflect.Uint64:
|
||||
return tomlInteger
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return tomlFloat
|
||||
case reflect.Array, reflect.Slice:
|
||||
if typeEqual(tomlHash, tomlArrayType(rv)) {
|
||||
return tomlArrayHash
|
||||
}
|
||||
return tomlArray
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
return tomlTypeOfGo(rv.Elem())
|
||||
case reflect.String:
|
||||
return tomlString
|
||||
case reflect.Map:
|
||||
return tomlHash
|
||||
case reflect.Struct:
|
||||
switch rv.Interface().(type) {
|
||||
case time.Time:
|
||||
return tomlDatetime
|
||||
case TextMarshaler:
|
||||
return tomlString
|
||||
default:
|
||||
return tomlHash
|
||||
}
|
||||
default:
|
||||
panic("unexpected reflect.Kind: " + rv.Kind().String())
|
||||
}
|
||||
}
|
||||
|
||||
// tomlArrayType returns the element type of a TOML array. The type returned
|
||||
// may be nil if it cannot be determined (e.g., a nil slice or a zero length
|
||||
// slize). This function may also panic if it finds a type that cannot be
|
||||
// expressed in TOML (such as nil elements, heterogeneous arrays or directly
|
||||
// nested arrays of tables).
|
||||
func tomlArrayType(rv reflect.Value) tomlType {
|
||||
if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
firstType := tomlTypeOfGo(rv.Index(0))
|
||||
if firstType == nil {
|
||||
encPanic(errArrayNilElement)
|
||||
}
|
||||
|
||||
rvlen := rv.Len()
|
||||
for i := 1; i < rvlen; i++ {
|
||||
elem := rv.Index(i)
|
||||
switch elemType := tomlTypeOfGo(elem); {
|
||||
case elemType == nil:
|
||||
encPanic(errArrayNilElement)
|
||||
case !typeEqual(firstType, elemType):
|
||||
encPanic(errArrayMixedElementTypes)
|
||||
}
|
||||
}
|
||||
// If we have a nested array, then we must make sure that the nested
|
||||
// array contains ONLY primitives.
|
||||
// This checks arbitrarily nested arrays.
|
||||
if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) {
|
||||
nest := tomlArrayType(eindirect(rv.Index(0)))
|
||||
if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) {
|
||||
encPanic(errArrayNoTable)
|
||||
}
|
||||
}
|
||||
return firstType
|
||||
}
|
||||
|
||||
type tagOptions struct {
|
||||
skip bool // "-"
|
||||
name string
|
||||
omitempty bool
|
||||
omitzero bool
|
||||
}
|
||||
|
||||
func getOptions(tag reflect.StructTag) tagOptions {
|
||||
t := tag.Get("toml")
|
||||
if t == "-" {
|
||||
return tagOptions{skip: true}
|
||||
}
|
||||
var opts tagOptions
|
||||
parts := strings.Split(t, ",")
|
||||
opts.name = parts[0]
|
||||
for _, s := range parts[1:] {
|
||||
switch s {
|
||||
case "omitempty":
|
||||
opts.omitempty = true
|
||||
case "omitzero":
|
||||
opts.omitzero = true
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func isZero(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return rv.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return rv.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return rv.Float() == 0.0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isEmpty(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||
return rv.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !rv.Bool()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (enc *Encoder) newline() {
|
||||
if enc.hasWritten {
|
||||
enc.wf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) keyEqElement(key Key, val reflect.Value) {
|
||||
if len(key) == 0 {
|
||||
encPanic(errNoKey)
|
||||
}
|
||||
panicIfInvalidKey(key)
|
||||
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
|
||||
enc.eElement(val)
|
||||
enc.newline()
|
||||
}
|
||||
|
||||
func (enc *Encoder) wf(format string, v ...interface{}) {
|
||||
if _, err := fmt.Fprintf(enc.w, format, v...); err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
enc.hasWritten = true
|
||||
}
|
||||
|
||||
func (enc *Encoder) indentStr(key Key) string {
|
||||
return strings.Repeat(enc.Indent, len(key)-1)
|
||||
}
|
||||
|
||||
func encPanic(err error) {
|
||||
panic(tomlEncodeError{err})
|
||||
}
|
||||
|
||||
func eindirect(v reflect.Value) reflect.Value {
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
return eindirect(v.Elem())
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func isNil(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func panicIfInvalidKey(key Key) {
|
||||
for _, k := range key {
|
||||
if len(k) == 0 {
|
||||
encPanic(e("Key '%s' is not a valid table name. Key names "+
|
||||
"cannot be empty.", key.maybeQuotedAll()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isValidKeyName(s string) bool {
|
||||
return len(s) != 0
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// +build go1.2
|
||||
|
||||
package toml
|
||||
|
||||
// In order to support Go 1.1, we define our own TextMarshaler and
|
||||
// TextUnmarshaler types. For Go 1.2+, we just alias them with the
|
||||
// standard library interfaces.
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
)
|
||||
|
||||
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
|
||||
// so that Go 1.1 can be supported.
|
||||
type TextMarshaler encoding.TextMarshaler
|
||||
|
||||
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
|
||||
// here so that Go 1.1 can be supported.
|
||||
type TextUnmarshaler encoding.TextUnmarshaler
|
18
tests/tools/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
generated
vendored
Normal file
18
tests/tools/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
// +build !go1.2
|
||||
|
||||
package toml
|
||||
|
||||
// These interfaces were introduced in Go 1.2, so we add them manually when
|
||||
// compiling for Go 1.1.
|
||||
|
||||
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
|
||||
// so that Go 1.1 can be supported.
|
||||
type TextMarshaler interface {
|
||||
MarshalText() (text []byte, err error)
|
||||
}
|
||||
|
||||
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
|
||||
// here so that Go 1.1 can be supported.
|
||||
type TextUnmarshaler interface {
|
||||
UnmarshalText(text []byte) error
|
||||
}
|
|
@ -0,0 +1,953 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type itemType int
|
||||
|
||||
const (
|
||||
itemError itemType = iota
|
||||
itemNIL // used in the parser to indicate no type
|
||||
itemEOF
|
||||
itemText
|
||||
itemString
|
||||
itemRawString
|
||||
itemMultilineString
|
||||
itemRawMultilineString
|
||||
itemBool
|
||||
itemInteger
|
||||
itemFloat
|
||||
itemDatetime
|
||||
itemArray // the start of an array
|
||||
itemArrayEnd
|
||||
itemTableStart
|
||||
itemTableEnd
|
||||
itemArrayTableStart
|
||||
itemArrayTableEnd
|
||||
itemKeyStart
|
||||
itemCommentStart
|
||||
itemInlineTableStart
|
||||
itemInlineTableEnd
|
||||
)
|
||||
|
||||
const (
|
||||
eof = 0
|
||||
comma = ','
|
||||
tableStart = '['
|
||||
tableEnd = ']'
|
||||
arrayTableStart = '['
|
||||
arrayTableEnd = ']'
|
||||
tableSep = '.'
|
||||
keySep = '='
|
||||
arrayStart = '['
|
||||
arrayEnd = ']'
|
||||
commentStart = '#'
|
||||
stringStart = '"'
|
||||
stringEnd = '"'
|
||||
rawStringStart = '\''
|
||||
rawStringEnd = '\''
|
||||
inlineTableStart = '{'
|
||||
inlineTableEnd = '}'
|
||||
)
|
||||
|
||||
type stateFn func(lx *lexer) stateFn
|
||||
|
||||
type lexer struct {
|
||||
input string
|
||||
start int
|
||||
pos int
|
||||
line int
|
||||
state stateFn
|
||||
items chan item
|
||||
|
||||
// Allow for backing up up to three runes.
|
||||
// This is necessary because TOML contains 3-rune tokens (""" and ''').
|
||||
prevWidths [3]int
|
||||
nprev int // how many of prevWidths are in use
|
||||
// If we emit an eof, we can still back up, but it is not OK to call
|
||||
// next again.
|
||||
atEOF bool
|
||||
|
||||
// A stack of state functions used to maintain context.
|
||||
// The idea is to reuse parts of the state machine in various places.
|
||||
// For example, values can appear at the top level or within arbitrarily
|
||||
// nested arrays. The last state on the stack is used after a value has
|
||||
// been lexed. Similarly for comments.
|
||||
stack []stateFn
|
||||
}
|
||||
|
||||
type item struct {
|
||||
typ itemType
|
||||
val string
|
||||
line int
|
||||
}
|
||||
|
||||
func (lx *lexer) nextItem() item {
|
||||
for {
|
||||
select {
|
||||
case item := <-lx.items:
|
||||
return item
|
||||
default:
|
||||
lx.state = lx.state(lx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lex(input string) *lexer {
|
||||
lx := &lexer{
|
||||
input: input,
|
||||
state: lexTop,
|
||||
line: 1,
|
||||
items: make(chan item, 10),
|
||||
stack: make([]stateFn, 0, 10),
|
||||
}
|
||||
return lx
|
||||
}
|
||||
|
||||
func (lx *lexer) push(state stateFn) {
|
||||
lx.stack = append(lx.stack, state)
|
||||
}
|
||||
|
||||
func (lx *lexer) pop() stateFn {
|
||||
if len(lx.stack) == 0 {
|
||||
return lx.errorf("BUG in lexer: no states to pop")
|
||||
}
|
||||
last := lx.stack[len(lx.stack)-1]
|
||||
lx.stack = lx.stack[0 : len(lx.stack)-1]
|
||||
return last
|
||||
}
|
||||
|
||||
func (lx *lexer) current() string {
|
||||
return lx.input[lx.start:lx.pos]
|
||||
}
|
||||
|
||||
func (lx *lexer) emit(typ itemType) {
|
||||
lx.items <- item{typ, lx.current(), lx.line}
|
||||
lx.start = lx.pos
|
||||
}
|
||||
|
||||
func (lx *lexer) emitTrim(typ itemType) {
|
||||
lx.items <- item{typ, strings.TrimSpace(lx.current()), lx.line}
|
||||
lx.start = lx.pos
|
||||
}
|
||||
|
||||
func (lx *lexer) next() (r rune) {
|
||||
if lx.atEOF {
|
||||
panic("next called after EOF")
|
||||
}
|
||||
if lx.pos >= len(lx.input) {
|
||||
lx.atEOF = true
|
||||
return eof
|
||||
}
|
||||
|
||||
if lx.input[lx.pos] == '\n' {
|
||||
lx.line++
|
||||
}
|
||||
lx.prevWidths[2] = lx.prevWidths[1]
|
||||
lx.prevWidths[1] = lx.prevWidths[0]
|
||||
if lx.nprev < 3 {
|
||||
lx.nprev++
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(lx.input[lx.pos:])
|
||||
lx.prevWidths[0] = w
|
||||
lx.pos += w
|
||||
return r
|
||||
}
|
||||
|
||||
// ignore skips over the pending input before this point.
|
||||
func (lx *lexer) ignore() {
|
||||
lx.start = lx.pos
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can be called only twice between calls to next.
|
||||
func (lx *lexer) backup() {
|
||||
if lx.atEOF {
|
||||
lx.atEOF = false
|
||||
return
|
||||
}
|
||||
if lx.nprev < 1 {
|
||||
panic("backed up too far")
|
||||
}
|
||||
w := lx.prevWidths[0]
|
||||
lx.prevWidths[0] = lx.prevWidths[1]
|
||||
lx.prevWidths[1] = lx.prevWidths[2]
|
||||
lx.nprev--
|
||||
lx.pos -= w
|
||||
if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
|
||||
lx.line--
|
||||
}
|
||||
}
|
||||
|
||||
// accept consumes the next rune if it's equal to `valid`.
|
||||
func (lx *lexer) accept(valid rune) bool {
|
||||
if lx.next() == valid {
|
||||
return true
|
||||
}
|
||||
lx.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next rune in the input.
|
||||
func (lx *lexer) peek() rune {
|
||||
r := lx.next()
|
||||
lx.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// skip ignores all input that matches the given predicate.
|
||||
func (lx *lexer) skip(pred func(rune) bool) {
|
||||
for {
|
||||
r := lx.next()
|
||||
if pred(r) {
|
||||
continue
|
||||
}
|
||||
lx.backup()
|
||||
lx.ignore()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// errorf stops all lexing by emitting an error and returning `nil`.
|
||||
// Note that any value that is a character is escaped if it's a special
|
||||
// character (newlines, tabs, etc.).
|
||||
func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
|
||||
lx.items <- item{
|
||||
itemError,
|
||||
fmt.Sprintf(format, values...),
|
||||
lx.line,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// lexTop consumes elements at the top level of TOML data.
|
||||
func lexTop(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isWhitespace(r) || isNL(r) {
|
||||
return lexSkip(lx, lexTop)
|
||||
}
|
||||
switch r {
|
||||
case commentStart:
|
||||
lx.push(lexTop)
|
||||
return lexCommentStart
|
||||
case tableStart:
|
||||
return lexTableStart
|
||||
case eof:
|
||||
if lx.pos > lx.start {
|
||||
return lx.errorf("unexpected EOF")
|
||||
}
|
||||
lx.emit(itemEOF)
|
||||
return nil
|
||||
}
|
||||
|
||||
// At this point, the only valid item can be a key, so we back up
|
||||
// and let the key lexer do the rest.
|
||||
lx.backup()
|
||||
lx.push(lexTopEnd)
|
||||
return lexKeyStart
|
||||
}
|
||||
|
||||
// lexTopEnd is entered whenever a top-level item has been consumed. (A value
|
||||
// or a table.) It must see only whitespace, and will turn back to lexTop
|
||||
// upon a newline. If it sees EOF, it will quit the lexer successfully.
|
||||
func lexTopEnd(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case r == commentStart:
|
||||
// a comment will read to a newline for us.
|
||||
lx.push(lexTop)
|
||||
return lexCommentStart
|
||||
case isWhitespace(r):
|
||||
return lexTopEnd
|
||||
case isNL(r):
|
||||
lx.ignore()
|
||||
return lexTop
|
||||
case r == eof:
|
||||
lx.emit(itemEOF)
|
||||
return nil
|
||||
}
|
||||
return lx.errorf("expected a top-level item to end with a newline, "+
|
||||
"comment, or EOF, but got %q instead", r)
|
||||
}
|
||||
|
||||
// lexTable lexes the beginning of a table. Namely, it makes sure that
|
||||
// it starts with a character other than '.' and ']'.
|
||||
// It assumes that '[' has already been consumed.
|
||||
// It also handles the case that this is an item in an array of tables.
|
||||
// e.g., '[[name]]'.
|
||||
func lexTableStart(lx *lexer) stateFn {
|
||||
if lx.peek() == arrayTableStart {
|
||||
lx.next()
|
||||
lx.emit(itemArrayTableStart)
|
||||
lx.push(lexArrayTableEnd)
|
||||
} else {
|
||||
lx.emit(itemTableStart)
|
||||
lx.push(lexTableEnd)
|
||||
}
|
||||
return lexTableNameStart
|
||||
}
|
||||
|
||||
func lexTableEnd(lx *lexer) stateFn {
|
||||
lx.emit(itemTableEnd)
|
||||
return lexTopEnd
|
||||
}
|
||||
|
||||
func lexArrayTableEnd(lx *lexer) stateFn {
|
||||
if r := lx.next(); r != arrayTableEnd {
|
||||
return lx.errorf("expected end of table array name delimiter %q, "+
|
||||
"but got %q instead", arrayTableEnd, r)
|
||||
}
|
||||
lx.emit(itemArrayTableEnd)
|
||||
return lexTopEnd
|
||||
}
|
||||
|
||||
func lexTableNameStart(lx *lexer) stateFn {
|
||||
lx.skip(isWhitespace)
|
||||
switch r := lx.peek(); {
|
||||
case r == tableEnd || r == eof:
|
||||
return lx.errorf("unexpected end of table name " +
|
||||
"(table names cannot be empty)")
|
||||
case r == tableSep:
|
||||
return lx.errorf("unexpected table separator " +
|
||||
"(table names cannot be empty)")
|
||||
case r == stringStart || r == rawStringStart:
|
||||
lx.ignore()
|
||||
lx.push(lexTableNameEnd)
|
||||
return lexValue // reuse string lexing
|
||||
default:
|
||||
return lexBareTableName
|
||||
}
|
||||
}
|
||||
|
||||
// lexBareTableName lexes the name of a table. It assumes that at least one
|
||||
// valid character for the table has already been read.
|
||||
func lexBareTableName(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isBareKeyChar(r) {
|
||||
return lexBareTableName
|
||||
}
|
||||
lx.backup()
|
||||
lx.emit(itemText)
|
||||
return lexTableNameEnd
|
||||
}
|
||||
|
||||
// lexTableNameEnd reads the end of a piece of a table name, optionally
|
||||
// consuming whitespace.
|
||||
func lexTableNameEnd(lx *lexer) stateFn {
|
||||
lx.skip(isWhitespace)
|
||||
switch r := lx.next(); {
|
||||
case isWhitespace(r):
|
||||
return lexTableNameEnd
|
||||
case r == tableSep:
|
||||
lx.ignore()
|
||||
return lexTableNameStart
|
||||
case r == tableEnd:
|
||||
return lx.pop()
|
||||
default:
|
||||
return lx.errorf("expected '.' or ']' to end table name, "+
|
||||
"but got %q instead", r)
|
||||
}
|
||||
}
|
||||
|
||||
// lexKeyStart consumes a key name up until the first non-whitespace character.
|
||||
// lexKeyStart will ignore whitespace.
|
||||
func lexKeyStart(lx *lexer) stateFn {
|
||||
r := lx.peek()
|
||||
switch {
|
||||
case r == keySep:
|
||||
return lx.errorf("unexpected key separator %q", keySep)
|
||||
case isWhitespace(r) || isNL(r):
|
||||
lx.next()
|
||||
return lexSkip(lx, lexKeyStart)
|
||||
case r == stringStart || r == rawStringStart:
|
||||
lx.ignore()
|
||||
lx.emit(itemKeyStart)
|
||||
lx.push(lexKeyEnd)
|
||||
return lexValue // reuse string lexing
|
||||
default:
|
||||
lx.ignore()
|
||||
lx.emit(itemKeyStart)
|
||||
return lexBareKey
|
||||
}
|
||||
}
|
||||
|
||||
// lexBareKey consumes the text of a bare key. Assumes that the first character
|
||||
// (which is not whitespace) has not yet been consumed.
|
||||
func lexBareKey(lx *lexer) stateFn {
|
||||
switch r := lx.next(); {
|
||||
case isBareKeyChar(r):
|
||||
return lexBareKey
|
||||
case isWhitespace(r):
|
||||
lx.backup()
|
||||
lx.emit(itemText)
|
||||
return lexKeyEnd
|
||||
case r == keySep:
|
||||
lx.backup()
|
||||
lx.emit(itemText)
|
||||
return lexKeyEnd
|
||||
default:
|
||||
return lx.errorf("bare keys cannot contain %q", r)
|
||||
}
|
||||
}
|
||||
|
||||
// lexKeyEnd consumes the end of a key and trims whitespace (up to the key
|
||||
// separator).
|
||||
func lexKeyEnd(lx *lexer) stateFn {
|
||||
switch r := lx.next(); {
|
||||
case r == keySep:
|
||||
return lexSkip(lx, lexValue)
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexKeyEnd)
|
||||
default:
|
||||
return lx.errorf("expected key separator %q, but got %q instead",
|
||||
keySep, r)
|
||||
}
|
||||
}
|
||||
|
||||
// lexValue starts the consumption of a value anywhere a value is expected.
|
||||
// lexValue will ignore whitespace.
|
||||
// After a value is lexed, the last state on the next is popped and returned.
|
||||
func lexValue(lx *lexer) stateFn {
|
||||
// We allow whitespace to precede a value, but NOT newlines.
|
||||
// In array syntax, the array states are responsible for ignoring newlines.
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexValue)
|
||||
case isDigit(r):
|
||||
lx.backup() // avoid an extra state and use the same as above
|
||||
return lexNumberOrDateStart
|
||||
}
|
||||
switch r {
|
||||
case arrayStart:
|
||||
lx.ignore()
|
||||
lx.emit(itemArray)
|
||||
return lexArrayValue
|
||||
case inlineTableStart:
|
||||
lx.ignore()
|
||||
lx.emit(itemInlineTableStart)
|
||||
return lexInlineTableValue
|
||||
case stringStart:
|
||||
if lx.accept(stringStart) {
|
||||
if lx.accept(stringStart) {
|
||||
lx.ignore() // Ignore """
|
||||
return lexMultilineString
|
||||
}
|
||||
lx.backup()
|
||||
}
|
||||
lx.ignore() // ignore the '"'
|
||||
return lexString
|
||||
case rawStringStart:
|
||||
if lx.accept(rawStringStart) {
|
||||
if lx.accept(rawStringStart) {
|
||||
lx.ignore() // Ignore """
|
||||
return lexMultilineRawString
|
||||
}
|
||||
lx.backup()
|
||||
}
|
||||
lx.ignore() // ignore the "'"
|
||||
return lexRawString
|
||||
case '+', '-':
|
||||
return lexNumberStart
|
||||
case '.': // special error case, be kind to users
|
||||
return lx.errorf("floats must start with a digit, not '.'")
|
||||
}
|
||||
if unicode.IsLetter(r) {
|
||||
// Be permissive here; lexBool will give a nice error if the
|
||||
// user wrote something like
|
||||
// x = foo
|
||||
// (i.e. not 'true' or 'false' but is something else word-like.)
|
||||
lx.backup()
|
||||
return lexBool
|
||||
}
|
||||
return lx.errorf("expected value but found %q instead", r)
|
||||
}
|
||||
|
||||
// lexArrayValue consumes one value in an array. It assumes that '[' or ','
|
||||
// have already been consumed. All whitespace and newlines are ignored.
|
||||
func lexArrayValue(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isWhitespace(r) || isNL(r):
|
||||
return lexSkip(lx, lexArrayValue)
|
||||
case r == commentStart:
|
||||
lx.push(lexArrayValue)
|
||||
return lexCommentStart
|
||||
case r == comma:
|
||||
return lx.errorf("unexpected comma")
|
||||
case r == arrayEnd:
|
||||
// NOTE(caleb): The spec isn't clear about whether you can have
|
||||
// a trailing comma or not, so we'll allow it.
|
||||
return lexArrayEnd
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.push(lexArrayValueEnd)
|
||||
return lexValue
|
||||
}
|
||||
|
||||
// lexArrayValueEnd consumes everything between the end of an array value and
|
||||
// the next value (or the end of the array): it ignores whitespace and newlines
|
||||
// and expects either a ',' or a ']'.
|
||||
func lexArrayValueEnd(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isWhitespace(r) || isNL(r):
|
||||
return lexSkip(lx, lexArrayValueEnd)
|
||||
case r == commentStart:
|
||||
lx.push(lexArrayValueEnd)
|
||||
return lexCommentStart
|
||||
case r == comma:
|
||||
lx.ignore()
|
||||
return lexArrayValue // move on to the next value
|
||||
case r == arrayEnd:
|
||||
return lexArrayEnd
|
||||
}
|
||||
return lx.errorf(
|
||||
"expected a comma or array terminator %q, but got %q instead",
|
||||
arrayEnd, r,
|
||||
)
|
||||
}
|
||||
|
||||
// lexArrayEnd finishes the lexing of an array.
|
||||
// It assumes that a ']' has just been consumed.
|
||||
func lexArrayEnd(lx *lexer) stateFn {
|
||||
lx.ignore()
|
||||
lx.emit(itemArrayEnd)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexInlineTableValue consumes one key/value pair in an inline table.
|
||||
// It assumes that '{' or ',' have already been consumed. Whitespace is ignored.
|
||||
func lexInlineTableValue(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexInlineTableValue)
|
||||
case isNL(r):
|
||||
return lx.errorf("newlines not allowed within inline tables")
|
||||
case r == commentStart:
|
||||
lx.push(lexInlineTableValue)
|
||||
return lexCommentStart
|
||||
case r == comma:
|
||||
return lx.errorf("unexpected comma")
|
||||
case r == inlineTableEnd:
|
||||
return lexInlineTableEnd
|
||||
}
|
||||
lx.backup()
|
||||
lx.push(lexInlineTableValueEnd)
|
||||
return lexKeyStart
|
||||
}
|
||||
|
||||
// lexInlineTableValueEnd consumes everything between the end of an inline table
|
||||
// key/value pair and the next pair (or the end of the table):
|
||||
// it ignores whitespace and expects either a ',' or a '}'.
|
||||
func lexInlineTableValueEnd(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexInlineTableValueEnd)
|
||||
case isNL(r):
|
||||
return lx.errorf("newlines not allowed within inline tables")
|
||||
case r == commentStart:
|
||||
lx.push(lexInlineTableValueEnd)
|
||||
return lexCommentStart
|
||||
case r == comma:
|
||||
lx.ignore()
|
||||
return lexInlineTableValue
|
||||
case r == inlineTableEnd:
|
||||
return lexInlineTableEnd
|
||||
}
|
||||
return lx.errorf("expected a comma or an inline table terminator %q, "+
|
||||
"but got %q instead", inlineTableEnd, r)
|
||||
}
|
||||
|
||||
// lexInlineTableEnd finishes the lexing of an inline table.
|
||||
// It assumes that a '}' has just been consumed.
|
||||
func lexInlineTableEnd(lx *lexer) stateFn {
|
||||
lx.ignore()
|
||||
lx.emit(itemInlineTableEnd)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexString consumes the inner contents of a string. It assumes that the
|
||||
// beginning '"' has already been consumed and ignored.
|
||||
func lexString(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case r == eof:
|
||||
return lx.errorf("unexpected EOF")
|
||||
case isNL(r):
|
||||
return lx.errorf("strings cannot contain newlines")
|
||||
case r == '\\':
|
||||
lx.push(lexString)
|
||||
return lexStringEscape
|
||||
case r == stringEnd:
|
||||
lx.backup()
|
||||
lx.emit(itemString)
|
||||
lx.next()
|
||||
lx.ignore()
|
||||
return lx.pop()
|
||||
}
|
||||
return lexString
|
||||
}
|
||||
|
||||
// lexMultilineString consumes the inner contents of a string. It assumes that
|
||||
// the beginning '"""' has already been consumed and ignored.
|
||||
func lexMultilineString(lx *lexer) stateFn {
|
||||
switch lx.next() {
|
||||
case eof:
|
||||
return lx.errorf("unexpected EOF")
|
||||
case '\\':
|
||||
return lexMultilineStringEscape
|
||||
case stringEnd:
|
||||
if lx.accept(stringEnd) {
|
||||
if lx.accept(stringEnd) {
|
||||
lx.backup()
|
||||
lx.backup()
|
||||
lx.backup()
|
||||
lx.emit(itemMultilineString)
|
||||
lx.next()
|
||||
lx.next()
|
||||
lx.next()
|
||||
lx.ignore()
|
||||
return lx.pop()
|
||||
}
|
||||
lx.backup()
|
||||
}
|
||||
}
|
||||
return lexMultilineString
|
||||
}
|
||||
|
||||
// lexRawString consumes a raw string. Nothing can be escaped in such a string.
|
||||
// It assumes that the beginning "'" has already been consumed and ignored.
|
||||
func lexRawString(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case r == eof:
|
||||
return lx.errorf("unexpected EOF")
|
||||
case isNL(r):
|
||||
return lx.errorf("strings cannot contain newlines")
|
||||
case r == rawStringEnd:
|
||||
lx.backup()
|
||||
lx.emit(itemRawString)
|
||||
lx.next()
|
||||
lx.ignore()
|
||||
return lx.pop()
|
||||
}
|
||||
return lexRawString
|
||||
}
|
||||
|
||||
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such
|
||||
// a string. It assumes that the beginning "'''" has already been consumed and
|
||||
// ignored.
|
||||
func lexMultilineRawString(lx *lexer) stateFn {
|
||||
switch lx.next() {
|
||||
case eof:
|
||||
return lx.errorf("unexpected EOF")
|
||||
case rawStringEnd:
|
||||
if lx.accept(rawStringEnd) {
|
||||
if lx.accept(rawStringEnd) {
|
||||
lx.backup()
|
||||
lx.backup()
|
||||
lx.backup()
|
||||
lx.emit(itemRawMultilineString)
|
||||
lx.next()
|
||||
lx.next()
|
||||
lx.next()
|
||||
lx.ignore()
|
||||
return lx.pop()
|
||||
}
|
||||
lx.backup()
|
||||
}
|
||||
}
|
||||
return lexMultilineRawString
|
||||
}
|
||||
|
||||
// lexMultilineStringEscape consumes an escaped character. It assumes that the
|
||||
// preceding '\\' has already been consumed.
|
||||
func lexMultilineStringEscape(lx *lexer) stateFn {
|
||||
// Handle the special case first:
|
||||
if isNL(lx.next()) {
|
||||
return lexMultilineString
|
||||
}
|
||||
lx.backup()
|
||||
lx.push(lexMultilineString)
|
||||
return lexStringEscape(lx)
|
||||
}
|
||||
|
||||
func lexStringEscape(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch r {
|
||||
case 'b':
|
||||
fallthrough
|
||||
case 't':
|
||||
fallthrough
|
||||
case 'n':
|
||||
fallthrough
|
||||
case 'f':
|
||||
fallthrough
|
||||
case 'r':
|
||||
fallthrough
|
||||
case '"':
|
||||
fallthrough
|
||||
case '\\':
|
||||
return lx.pop()
|
||||
case 'u':
|
||||
return lexShortUnicodeEscape
|
||||
case 'U':
|
||||
return lexLongUnicodeEscape
|
||||
}
|
||||
return lx.errorf("invalid escape character %q; only the following "+
|
||||
"escape characters are allowed: "+
|
||||
`\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r)
|
||||
}
|
||||
|
||||
func lexShortUnicodeEscape(lx *lexer) stateFn {
|
||||
var r rune
|
||||
for i := 0; i < 4; i++ {
|
||||
r = lx.next()
|
||||
if !isHexadecimal(r) {
|
||||
return lx.errorf(`expected four hexadecimal digits after '\u', `+
|
||||
"but got %q instead", lx.current())
|
||||
}
|
||||
}
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
func lexLongUnicodeEscape(lx *lexer) stateFn {
|
||||
var r rune
|
||||
for i := 0; i < 8; i++ {
|
||||
r = lx.next()
|
||||
if !isHexadecimal(r) {
|
||||
return lx.errorf(`expected eight hexadecimal digits after '\U', `+
|
||||
"but got %q instead", lx.current())
|
||||
}
|
||||
}
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexNumberOrDateStart consumes either an integer, a float, or datetime.
|
||||
func lexNumberOrDateStart(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexNumberOrDate
|
||||
}
|
||||
switch r {
|
||||
case '_':
|
||||
return lexNumber
|
||||
case 'e', 'E':
|
||||
return lexFloat
|
||||
case '.':
|
||||
return lx.errorf("floats must start with a digit, not '.'")
|
||||
}
|
||||
return lx.errorf("expected a digit but got %q", r)
|
||||
}
|
||||
|
||||
// lexNumberOrDate consumes either an integer, float or datetime.
|
||||
func lexNumberOrDate(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexNumberOrDate
|
||||
}
|
||||
switch r {
|
||||
case '-':
|
||||
return lexDatetime
|
||||
case '_':
|
||||
return lexNumber
|
||||
case '.', 'e', 'E':
|
||||
return lexFloat
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.emit(itemInteger)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexDatetime consumes a Datetime, to a first approximation.
|
||||
// The parser validates that it matches one of the accepted formats.
|
||||
func lexDatetime(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexDatetime
|
||||
}
|
||||
switch r {
|
||||
case '-', 'T', ':', '.', 'Z', '+':
|
||||
return lexDatetime
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.emit(itemDatetime)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexNumberStart consumes either an integer or a float. It assumes that a sign
|
||||
// has already been read, but that *no* digits have been consumed.
|
||||
// lexNumberStart will move to the appropriate integer or float states.
|
||||
func lexNumberStart(lx *lexer) stateFn {
|
||||
// We MUST see a digit. Even floats have to start with a digit.
|
||||
r := lx.next()
|
||||
if !isDigit(r) {
|
||||
if r == '.' {
|
||||
return lx.errorf("floats must start with a digit, not '.'")
|
||||
}
|
||||
return lx.errorf("expected a digit but got %q", r)
|
||||
}
|
||||
return lexNumber
|
||||
}
|
||||
|
||||
// lexNumber consumes an integer or a float after seeing the first digit.
|
||||
func lexNumber(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexNumber
|
||||
}
|
||||
switch r {
|
||||
case '_':
|
||||
return lexNumber
|
||||
case '.', 'e', 'E':
|
||||
return lexFloat
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.emit(itemInteger)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexFloat consumes the elements of a float. It allows any sequence of
|
||||
// float-like characters, so floats emitted by the lexer are only a first
|
||||
// approximation and must be validated by the parser.
|
||||
func lexFloat(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexFloat
|
||||
}
|
||||
switch r {
|
||||
case '_', '.', '-', '+', 'e', 'E':
|
||||
return lexFloat
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.emit(itemFloat)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexBool consumes a bool string: 'true' or 'false.
|
||||
func lexBool(lx *lexer) stateFn {
|
||||
var rs []rune
|
||||
for {
|
||||
r := lx.next()
|
||||
if !unicode.IsLetter(r) {
|
||||
lx.backup()
|
||||
break
|
||||
}
|
||||
rs = append(rs, r)
|
||||
}
|
||||
s := string(rs)
|
||||
switch s {
|
||||
case "true", "false":
|
||||
lx.emit(itemBool)
|
||||
return lx.pop()
|
||||
}
|
||||
return lx.errorf("expected value but found %q instead", s)
|
||||
}
|
||||
|
||||
// lexCommentStart begins the lexing of a comment. It will emit
|
||||
// itemCommentStart and consume no characters, passing control to lexComment.
|
||||
func lexCommentStart(lx *lexer) stateFn {
|
||||
lx.ignore()
|
||||
lx.emit(itemCommentStart)
|
||||
return lexComment
|
||||
}
|
||||
|
||||
// lexComment lexes an entire comment. It assumes that '#' has been consumed.
|
||||
// It will consume *up to* the first newline character, and pass control
|
||||
// back to the last state on the stack.
|
||||
func lexComment(lx *lexer) stateFn {
|
||||
r := lx.peek()
|
||||
if isNL(r) || r == eof {
|
||||
lx.emit(itemText)
|
||||
return lx.pop()
|
||||
}
|
||||
lx.next()
|
||||
return lexComment
|
||||
}
|
||||
|
||||
// lexSkip ignores all slurped input and moves on to the next state.
|
||||
func lexSkip(lx *lexer, nextState stateFn) stateFn {
|
||||
return func(lx *lexer) stateFn {
|
||||
lx.ignore()
|
||||
return nextState
|
||||
}
|
||||
}
|
||||
|
||||
// isWhitespace returns true if `r` is a whitespace character according
|
||||
// to the spec.
|
||||
func isWhitespace(r rune) bool {
|
||||
return r == '\t' || r == ' '
|
||||
}
|
||||
|
||||
func isNL(r rune) bool {
|
||||
return r == '\n' || r == '\r'
|
||||
}
|
||||
|
||||
func isDigit(r rune) bool {
|
||||
return r >= '0' && r <= '9'
|
||||
}
|
||||
|
||||
func isHexadecimal(r rune) bool {
|
||||
return (r >= '0' && r <= '9') ||
|
||||
(r >= 'a' && r <= 'f') ||
|
||||
(r >= 'A' && r <= 'F')
|
||||
}
|
||||
|
||||
func isBareKeyChar(r rune) bool {
|
||||
return (r >= 'A' && r <= 'Z') ||
|
||||
(r >= 'a' && r <= 'z') ||
|
||||
(r >= '0' && r <= '9') ||
|
||||
r == '_' ||
|
||||
r == '-'
|
||||
}
|
||||
|
||||
func (itype itemType) String() string {
|
||||
switch itype {
|
||||
case itemError:
|
||||
return "Error"
|
||||
case itemNIL:
|
||||
return "NIL"
|
||||
case itemEOF:
|
||||
return "EOF"
|
||||
case itemText:
|
||||
return "Text"
|
||||
case itemString, itemRawString, itemMultilineString, itemRawMultilineString:
|
||||
return "String"
|
||||
case itemBool:
|
||||
return "Bool"
|
||||
case itemInteger:
|
||||
return "Integer"
|
||||
case itemFloat:
|
||||
return "Float"
|
||||
case itemDatetime:
|
||||
return "DateTime"
|
||||
case itemTableStart:
|
||||
return "TableStart"
|
||||
case itemTableEnd:
|
||||
return "TableEnd"
|
||||
case itemKeyStart:
|
||||
return "KeyStart"
|
||||
case itemArray:
|
||||
return "Array"
|
||||
case itemArrayEnd:
|
||||
return "ArrayEnd"
|
||||
case itemCommentStart:
|
||||
return "CommentStart"
|
||||
}
|
||||
panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype)))
|
||||
}
|
||||
|
||||
func (item item) String() string {
|
||||
return fmt.Sprintf("(%s, %s)", item.typ.String(), item.val)
|
||||
}
|
|
@ -0,0 +1,592 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
mapping map[string]interface{}
|
||||
types map[string]tomlType
|
||||
lx *lexer
|
||||
|
||||
// A list of keys in the order that they appear in the TOML data.
|
||||
ordered []Key
|
||||
|
||||
// the full key for the current hash in scope
|
||||
context Key
|
||||
|
||||
// the base key name for everything except hashes
|
||||
currentKey string
|
||||
|
||||
// rough approximation of line number
|
||||
approxLine int
|
||||
|
||||
// A map of 'key.group.names' to whether they were created implicitly.
|
||||
implicits map[string]bool
|
||||
}
|
||||
|
||||
type parseError string
|
||||
|
||||
func (pe parseError) Error() string {
|
||||
return string(pe)
|
||||
}
|
||||
|
||||
func parse(data string) (p *parser, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
if err, ok = r.(parseError); ok {
|
||||
return
|
||||
}
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
p = &parser{
|
||||
mapping: make(map[string]interface{}),
|
||||
types: make(map[string]tomlType),
|
||||
lx: lex(data),
|
||||
ordered: make([]Key, 0),
|
||||
implicits: make(map[string]bool),
|
||||
}
|
||||
for {
|
||||
item := p.next()
|
||||
if item.typ == itemEOF {
|
||||
break
|
||||
}
|
||||
p.topLevel(item)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *parser) panicf(format string, v ...interface{}) {
|
||||
msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s",
|
||||
p.approxLine, p.current(), fmt.Sprintf(format, v...))
|
||||
panic(parseError(msg))
|
||||
}
|
||||
|
||||
func (p *parser) next() item {
|
||||
it := p.lx.nextItem()
|
||||
if it.typ == itemError {
|
||||
p.panicf("%s", it.val)
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) bug(format string, v ...interface{}) {
|
||||
panic(fmt.Sprintf("BUG: "+format+"\n\n", v...))
|
||||
}
|
||||
|
||||
func (p *parser) expect(typ itemType) item {
|
||||
it := p.next()
|
||||
p.assertEqual(typ, it.typ)
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) assertEqual(expected, got itemType) {
|
||||
if expected != got {
|
||||
p.bug("Expected '%s' but got '%s'.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) topLevel(item item) {
|
||||
switch item.typ {
|
||||
case itemCommentStart:
|
||||
p.approxLine = item.line
|
||||
p.expect(itemText)
|
||||
case itemTableStart:
|
||||
kg := p.next()
|
||||
p.approxLine = kg.line
|
||||
|
||||
var key Key
|
||||
for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() {
|
||||
key = append(key, p.keyString(kg))
|
||||
}
|
||||
p.assertEqual(itemTableEnd, kg.typ)
|
||||
|
||||
p.establishContext(key, false)
|
||||
p.setType("", tomlHash)
|
||||
p.ordered = append(p.ordered, key)
|
||||
case itemArrayTableStart:
|
||||
kg := p.next()
|
||||
p.approxLine = kg.line
|
||||
|
||||
var key Key
|
||||
for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() {
|
||||
key = append(key, p.keyString(kg))
|
||||
}
|
||||
p.assertEqual(itemArrayTableEnd, kg.typ)
|
||||
|
||||
p.establishContext(key, true)
|
||||
p.setType("", tomlArrayHash)
|
||||
p.ordered = append(p.ordered, key)
|
||||
case itemKeyStart:
|
||||
kname := p.next()
|
||||
p.approxLine = kname.line
|
||||
p.currentKey = p.keyString(kname)
|
||||
|
||||
val, typ := p.value(p.next())
|
||||
p.setValue(p.currentKey, val)
|
||||
p.setType(p.currentKey, typ)
|
||||
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||
p.currentKey = ""
|
||||
default:
|
||||
p.bug("Unexpected type at top level: %s", item.typ)
|
||||
}
|
||||
}
|
||||
|
||||
// Gets a string for a key (or part of a key in a table name).
|
||||
func (p *parser) keyString(it item) string {
|
||||
switch it.typ {
|
||||
case itemText:
|
||||
return it.val
|
||||
case itemString, itemMultilineString,
|
||||
itemRawString, itemRawMultilineString:
|
||||
s, _ := p.value(it)
|
||||
return s.(string)
|
||||
default:
|
||||
p.bug("Unexpected key type: %s", it.typ)
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// value translates an expected value from the lexer into a Go value wrapped
|
||||
// as an empty interface.
|
||||
func (p *parser) value(it item) (interface{}, tomlType) {
|
||||
switch it.typ {
|
||||
case itemString:
|
||||
return p.replaceEscapes(it.val), p.typeOfPrimitive(it)
|
||||
case itemMultilineString:
|
||||
trimmed := stripFirstNewline(stripEscapedWhitespace(it.val))
|
||||
return p.replaceEscapes(trimmed), p.typeOfPrimitive(it)
|
||||
case itemRawString:
|
||||
return it.val, p.typeOfPrimitive(it)
|
||||
case itemRawMultilineString:
|
||||
return stripFirstNewline(it.val), p.typeOfPrimitive(it)
|
||||
case itemBool:
|
||||
switch it.val {
|
||||
case "true":
|
||||
return true, p.typeOfPrimitive(it)
|
||||
case "false":
|
||||
return false, p.typeOfPrimitive(it)
|
||||
}
|
||||
p.bug("Expected boolean value, but got '%s'.", it.val)
|
||||
case itemInteger:
|
||||
if !numUnderscoresOK(it.val) {
|
||||
p.panicf("Invalid integer %q: underscores must be surrounded by digits",
|
||||
it.val)
|
||||
}
|
||||
val := strings.Replace(it.val, "_", "", -1)
|
||||
num, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
// Distinguish integer values. Normally, it'd be a bug if the lexer
|
||||
// provides an invalid integer, but it's possible that the number is
|
||||
// out of range of valid values (which the lexer cannot determine).
|
||||
// So mark the former as a bug but the latter as a legitimate user
|
||||
// error.
|
||||
if e, ok := err.(*strconv.NumError); ok &&
|
||||
e.Err == strconv.ErrRange {
|
||||
|
||||
p.panicf("Integer '%s' is out of the range of 64-bit "+
|
||||
"signed integers.", it.val)
|
||||
} else {
|
||||
p.bug("Expected integer value, but got '%s'.", it.val)
|
||||
}
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
case itemFloat:
|
||||
parts := strings.FieldsFunc(it.val, func(r rune) bool {
|
||||
switch r {
|
||||
case '.', 'e', 'E':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
for _, part := range parts {
|
||||
if !numUnderscoresOK(part) {
|
||||
p.panicf("Invalid float %q: underscores must be "+
|
||||
"surrounded by digits", it.val)
|
||||
}
|
||||
}
|
||||
if !numPeriodsOK(it.val) {
|
||||
// As a special case, numbers like '123.' or '1.e2',
|
||||
// which are valid as far as Go/strconv are concerned,
|
||||
// must be rejected because TOML says that a fractional
|
||||
// part consists of '.' followed by 1+ digits.
|
||||
p.panicf("Invalid float %q: '.' must be followed "+
|
||||
"by one or more digits", it.val)
|
||||
}
|
||||
val := strings.Replace(it.val, "_", "", -1)
|
||||
num, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok &&
|
||||
e.Err == strconv.ErrRange {
|
||||
|
||||
p.panicf("Float '%s' is out of the range of 64-bit "+
|
||||
"IEEE-754 floating-point numbers.", it.val)
|
||||
} else {
|
||||
p.panicf("Invalid float value: %q", it.val)
|
||||
}
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
case itemDatetime:
|
||||
var t time.Time
|
||||
var ok bool
|
||||
var err error
|
||||
for _, format := range []string{
|
||||
"2006-01-02T15:04:05Z07:00",
|
||||
"2006-01-02T15:04:05",
|
||||
"2006-01-02",
|
||||
} {
|
||||
t, err = time.ParseInLocation(format, it.val, time.Local)
|
||||
if err == nil {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
p.panicf("Invalid TOML Datetime: %q.", it.val)
|
||||
}
|
||||
return t, p.typeOfPrimitive(it)
|
||||
case itemArray:
|
||||
array := make([]interface{}, 0)
|
||||
types := make([]tomlType, 0)
|
||||
|
||||
for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
|
||||
if it.typ == itemCommentStart {
|
||||
p.expect(itemText)
|
||||
continue
|
||||
}
|
||||
|
||||
val, typ := p.value(it)
|
||||
array = append(array, val)
|
||||
types = append(types, typ)
|
||||
}
|
||||
return array, p.typeOfArray(types)
|
||||
case itemInlineTableStart:
|
||||
var (
|
||||
hash = make(map[string]interface{})
|
||||
outerContext = p.context
|
||||
outerKey = p.currentKey
|
||||
)
|
||||
|
||||
p.context = append(p.context, p.currentKey)
|
||||
p.currentKey = ""
|
||||
for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
|
||||
if it.typ != itemKeyStart {
|
||||
p.bug("Expected key start but instead found %q, around line %d",
|
||||
it.val, p.approxLine)
|
||||
}
|
||||
if it.typ == itemCommentStart {
|
||||
p.expect(itemText)
|
||||
continue
|
||||
}
|
||||
|
||||
// retrieve key
|
||||
k := p.next()
|
||||
p.approxLine = k.line
|
||||
kname := p.keyString(k)
|
||||
|
||||
// retrieve value
|
||||
p.currentKey = kname
|
||||
val, typ := p.value(p.next())
|
||||
// make sure we keep metadata up to date
|
||||
p.setType(kname, typ)
|
||||
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||
hash[kname] = val
|
||||
}
|
||||
p.context = outerContext
|
||||
p.currentKey = outerKey
|
||||
return hash, tomlHash
|
||||
}
|
||||
p.bug("Unexpected value type: %s", it.typ)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// numUnderscoresOK checks whether each underscore in s is surrounded by
|
||||
// characters that are not underscores.
|
||||
func numUnderscoresOK(s string) bool {
|
||||
accept := false
|
||||
for _, r := range s {
|
||||
if r == '_' {
|
||||
if !accept {
|
||||
return false
|
||||
}
|
||||
accept = false
|
||||
continue
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
// numPeriodsOK checks whether every period in s is followed by a digit.
|
||||
func numPeriodsOK(s string) bool {
|
||||
period := false
|
||||
for _, r := range s {
|
||||
if period && !isDigit(r) {
|
||||
return false
|
||||
}
|
||||
period = r == '.'
|
||||
}
|
||||
return !period
|
||||
}
|
||||
|
||||
// establishContext sets the current context of the parser,
|
||||
// where the context is either a hash or an array of hashes. Which one is
|
||||
// set depends on the value of the `array` parameter.
|
||||
//
|
||||
// Establishing the context also makes sure that the key isn't a duplicate, and
|
||||
// will create implicit hashes automatically.
|
||||
func (p *parser) establishContext(key Key, array bool) {
|
||||
var ok bool
|
||||
|
||||
// Always start at the top level and drill down for our context.
|
||||
hashContext := p.mapping
|
||||
keyContext := make(Key, 0)
|
||||
|
||||
// We only need implicit hashes for key[0:-1]
|
||||
for _, k := range key[0 : len(key)-1] {
|
||||
_, ok = hashContext[k]
|
||||
keyContext = append(keyContext, k)
|
||||
|
||||
// No key? Make an implicit hash and move on.
|
||||
if !ok {
|
||||
p.addImplicit(keyContext)
|
||||
hashContext[k] = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// If the hash context is actually an array of tables, then set
|
||||
// the hash context to the last element in that array.
|
||||
//
|
||||
// Otherwise, it better be a table, since this MUST be a key group (by
|
||||
// virtue of it not being the last element in a key).
|
||||
switch t := hashContext[k].(type) {
|
||||
case []map[string]interface{}:
|
||||
hashContext = t[len(t)-1]
|
||||
case map[string]interface{}:
|
||||
hashContext = t
|
||||
default:
|
||||
p.panicf("Key '%s' was already created as a hash.", keyContext)
|
||||
}
|
||||
}
|
||||
|
||||
p.context = keyContext
|
||||
if array {
|
||||
// If this is the first element for this array, then allocate a new
|
||||
// list of tables for it.
|
||||
k := key[len(key)-1]
|
||||
if _, ok := hashContext[k]; !ok {
|
||||
hashContext[k] = make([]map[string]interface{}, 0, 5)
|
||||
}
|
||||
|
||||
// Add a new table. But make sure the key hasn't already been used
|
||||
// for something else.
|
||||
if hash, ok := hashContext[k].([]map[string]interface{}); ok {
|
||||
hashContext[k] = append(hash, make(map[string]interface{}))
|
||||
} else {
|
||||
p.panicf("Key '%s' was already created and cannot be used as "+
|
||||
"an array.", keyContext)
|
||||
}
|
||||
} else {
|
||||
p.setValue(key[len(key)-1], make(map[string]interface{}))
|
||||
}
|
||||
p.context = append(p.context, key[len(key)-1])
|
||||
}
|
||||
|
||||
// setValue sets the given key to the given value in the current context.
|
||||
// It will make sure that the key hasn't already been defined, account for
|
||||
// implicit key groups.
|
||||
func (p *parser) setValue(key string, value interface{}) {
|
||||
var tmpHash interface{}
|
||||
var ok bool
|
||||
|
||||
hash := p.mapping
|
||||
keyContext := make(Key, 0)
|
||||
for _, k := range p.context {
|
||||
keyContext = append(keyContext, k)
|
||||
if tmpHash, ok = hash[k]; !ok {
|
||||
p.bug("Context for key '%s' has not been established.", keyContext)
|
||||
}
|
||||
switch t := tmpHash.(type) {
|
||||
case []map[string]interface{}:
|
||||
// The context is a table of hashes. Pick the most recent table
|
||||
// defined as the current hash.
|
||||
hash = t[len(t)-1]
|
||||
case map[string]interface{}:
|
||||
hash = t
|
||||
default:
|
||||
p.bug("Expected hash to have type 'map[string]interface{}', but "+
|
||||
"it has '%T' instead.", tmpHash)
|
||||
}
|
||||
}
|
||||
keyContext = append(keyContext, key)
|
||||
|
||||
if _, ok := hash[key]; ok {
|
||||
// Typically, if the given key has already been set, then we have
|
||||
// to raise an error since duplicate keys are disallowed. However,
|
||||
// it's possible that a key was previously defined implicitly. In this
|
||||
// case, it is allowed to be redefined concretely. (See the
|
||||
// `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.)
|
||||
//
|
||||
// But we have to make sure to stop marking it as an implicit. (So that
|
||||
// another redefinition provokes an error.)
|
||||
//
|
||||
// Note that since it has already been defined (as a hash), we don't
|
||||
// want to overwrite it. So our business is done.
|
||||
if p.isImplicit(keyContext) {
|
||||
p.removeImplicit(keyContext)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we have a concrete key trying to override a previous
|
||||
// key, which is *always* wrong.
|
||||
p.panicf("Key '%s' has already been defined.", keyContext)
|
||||
}
|
||||
hash[key] = value
|
||||
}
|
||||
|
||||
// setType sets the type of a particular value at a given key.
|
||||
// It should be called immediately AFTER setValue.
|
||||
//
|
||||
// Note that if `key` is empty, then the type given will be applied to the
|
||||
// current context (which is either a table or an array of tables).
|
||||
func (p *parser) setType(key string, typ tomlType) {
|
||||
keyContext := make(Key, 0, len(p.context)+1)
|
||||
for _, k := range p.context {
|
||||
keyContext = append(keyContext, k)
|
||||
}
|
||||
if len(key) > 0 { // allow type setting for hashes
|
||||
keyContext = append(keyContext, key)
|
||||
}
|
||||
p.types[keyContext.String()] = typ
|
||||
}
|
||||
|
||||
// addImplicit sets the given Key as having been created implicitly.
|
||||
func (p *parser) addImplicit(key Key) {
|
||||
p.implicits[key.String()] = true
|
||||
}
|
||||
|
||||
// removeImplicit stops tagging the given key as having been implicitly
|
||||
// created.
|
||||
func (p *parser) removeImplicit(key Key) {
|
||||
p.implicits[key.String()] = false
|
||||
}
|
||||
|
||||
// isImplicit returns true if the key group pointed to by the key was created
|
||||
// implicitly.
|
||||
func (p *parser) isImplicit(key Key) bool {
|
||||
return p.implicits[key.String()]
|
||||
}
|
||||
|
||||
// current returns the full key name of the current context.
|
||||
func (p *parser) current() string {
|
||||
if len(p.currentKey) == 0 {
|
||||
return p.context.String()
|
||||
}
|
||||
if len(p.context) == 0 {
|
||||
return p.currentKey
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", p.context, p.currentKey)
|
||||
}
|
||||
|
||||
func stripFirstNewline(s string) string {
|
||||
if len(s) == 0 || s[0] != '\n' {
|
||||
return s
|
||||
}
|
||||
return s[1:]
|
||||
}
|
||||
|
||||
func stripEscapedWhitespace(s string) string {
|
||||
esc := strings.Split(s, "\\\n")
|
||||
if len(esc) > 1 {
|
||||
for i := 1; i < len(esc); i++ {
|
||||
esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace)
|
||||
}
|
||||
}
|
||||
return strings.Join(esc, "")
|
||||
}
|
||||
|
||||
func (p *parser) replaceEscapes(str string) string {
|
||||
var replaced []rune
|
||||
s := []byte(str)
|
||||
r := 0
|
||||
for r < len(s) {
|
||||
if s[r] != '\\' {
|
||||
c, size := utf8.DecodeRune(s[r:])
|
||||
r += size
|
||||
replaced = append(replaced, c)
|
||||
continue
|
||||
}
|
||||
r += 1
|
||||
if r >= len(s) {
|
||||
p.bug("Escape sequence at end of string.")
|
||||
return ""
|
||||
}
|
||||
switch s[r] {
|
||||
default:
|
||||
p.bug("Expected valid escape code after \\, but got %q.", s[r])
|
||||
return ""
|
||||
case 'b':
|
||||
replaced = append(replaced, rune(0x0008))
|
||||
r += 1
|
||||
case 't':
|
||||
replaced = append(replaced, rune(0x0009))
|
||||
r += 1
|
||||
case 'n':
|
||||
replaced = append(replaced, rune(0x000A))
|
||||
r += 1
|
||||
case 'f':
|
||||
replaced = append(replaced, rune(0x000C))
|
||||
r += 1
|
||||
case 'r':
|
||||
replaced = append(replaced, rune(0x000D))
|
||||
r += 1
|
||||
case '"':
|
||||
replaced = append(replaced, rune(0x0022))
|
||||
r += 1
|
||||
case '\\':
|
||||
replaced = append(replaced, rune(0x005C))
|
||||
r += 1
|
||||
case 'u':
|
||||
// At this point, we know we have a Unicode escape of the form
|
||||
// `uXXXX` at [r, r+5). (Because the lexer guarantees this
|
||||
// for us.)
|
||||
escaped := p.asciiEscapeToUnicode(s[r+1 : r+5])
|
||||
replaced = append(replaced, escaped)
|
||||
r += 5
|
||||
case 'U':
|
||||
// At this point, we know we have a Unicode escape of the form
|
||||
// `uXXXX` at [r, r+9). (Because the lexer guarantees this
|
||||
// for us.)
|
||||
escaped := p.asciiEscapeToUnicode(s[r+1 : r+9])
|
||||
replaced = append(replaced, escaped)
|
||||
r += 9
|
||||
}
|
||||
}
|
||||
return string(replaced)
|
||||
}
|
||||
|
||||
func (p *parser) asciiEscapeToUnicode(bs []byte) rune {
|
||||
s := string(bs)
|
||||
hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32)
|
||||
if err != nil {
|
||||
p.bug("Could not parse '%s' as a hexadecimal number, but the "+
|
||||
"lexer claims it's OK: %s", s, err)
|
||||
}
|
||||
if !utf8.ValidRune(rune(hex)) {
|
||||
p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s)
|
||||
}
|
||||
return rune(hex)
|
||||
}
|
||||
|
||||
func isStringType(ty itemType) bool {
|
||||
return ty == itemString || ty == itemMultilineString ||
|
||||
ty == itemRawString || ty == itemRawMultilineString
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
au BufWritePost *.go silent!make tags > /dev/null 2>&1
|
|
@ -0,0 +1,91 @@
|
|||
package toml
|
||||
|
||||
// tomlType represents any Go type that corresponds to a TOML type.
|
||||
// While the first draft of the TOML spec has a simplistic type system that
|
||||
// probably doesn't need this level of sophistication, we seem to be militating
|
||||
// toward adding real composite types.
|
||||
type tomlType interface {
|
||||
typeString() string
|
||||
}
|
||||
|
||||
// typeEqual accepts any two types and returns true if they are equal.
|
||||
func typeEqual(t1, t2 tomlType) bool {
|
||||
if t1 == nil || t2 == nil {
|
||||
return false
|
||||
}
|
||||
return t1.typeString() == t2.typeString()
|
||||
}
|
||||
|
||||
func typeIsHash(t tomlType) bool {
|
||||
return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash)
|
||||
}
|
||||
|
||||
type tomlBaseType string
|
||||
|
||||
func (btype tomlBaseType) typeString() string {
|
||||
return string(btype)
|
||||
}
|
||||
|
||||
func (btype tomlBaseType) String() string {
|
||||
return btype.typeString()
|
||||
}
|
||||
|
||||
var (
|
||||
tomlInteger tomlBaseType = "Integer"
|
||||
tomlFloat tomlBaseType = "Float"
|
||||
tomlDatetime tomlBaseType = "Datetime"
|
||||
tomlString tomlBaseType = "String"
|
||||
tomlBool tomlBaseType = "Bool"
|
||||
tomlArray tomlBaseType = "Array"
|
||||
tomlHash tomlBaseType = "Hash"
|
||||
tomlArrayHash tomlBaseType = "ArrayHash"
|
||||
)
|
||||
|
||||
// typeOfPrimitive returns a tomlType of any primitive value in TOML.
|
||||
// Primitive values are: Integer, Float, Datetime, String and Bool.
|
||||
//
|
||||
// Passing a lexer item other than the following will cause a BUG message
|
||||
// to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime.
|
||||
func (p *parser) typeOfPrimitive(lexItem item) tomlType {
|
||||
switch lexItem.typ {
|
||||
case itemInteger:
|
||||
return tomlInteger
|
||||
case itemFloat:
|
||||
return tomlFloat
|
||||
case itemDatetime:
|
||||
return tomlDatetime
|
||||
case itemString:
|
||||
return tomlString
|
||||
case itemMultilineString:
|
||||
return tomlString
|
||||
case itemRawString:
|
||||
return tomlString
|
||||
case itemRawMultilineString:
|
||||
return tomlString
|
||||
case itemBool:
|
||||
return tomlBool
|
||||
}
|
||||
p.bug("Cannot infer primitive type of lex item '%s'.", lexItem)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// typeOfArray returns a tomlType for an array given a list of types of its
|
||||
// values.
|
||||
//
|
||||
// In the current spec, if an array is homogeneous, then its type is always
|
||||
// "Array". If the array is not homogeneous, an error is generated.
|
||||
func (p *parser) typeOfArray(types []tomlType) tomlType {
|
||||
// Empty arrays are cool.
|
||||
if len(types) == 0 {
|
||||
return tomlArray
|
||||
}
|
||||
|
||||
theType := types[0]
|
||||
for _, t := range types[1:] {
|
||||
if !typeEqual(theType, t) {
|
||||
p.panicf("Array contains values of type '%s' and '%s', but "+
|
||||
"arrays must be homogeneous.", theType, t)
|
||||
}
|
||||
}
|
||||
return tomlArray
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
package toml
|
||||
|
||||
// Struct field handling is adapted from code in encoding/json:
|
||||
//
|
||||
// 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 Go distribution.
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A field represents a single field found in a struct.
|
||||
type field struct {
|
||||
name string // the name of the field (`toml` tag included)
|
||||
tag bool // whether field has a `toml` tag
|
||||
index []int // represents the depth of an anonymous field
|
||||
typ reflect.Type // the type of the field
|
||||
}
|
||||
|
||||
// byName sorts field by name, breaking ties with depth,
|
||||
// then breaking ties with "name came from toml tag", then
|
||||
// breaking ties with index sequence.
|
||||
type byName []field
|
||||
|
||||
func (x byName) Len() int { return len(x) }
|
||||
|
||||
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (x byName) Less(i, j int) bool {
|
||||
if x[i].name != x[j].name {
|
||||
return x[i].name < x[j].name
|
||||
}
|
||||
if len(x[i].index) != len(x[j].index) {
|
||||
return len(x[i].index) < len(x[j].index)
|
||||
}
|
||||
if x[i].tag != x[j].tag {
|
||||
return x[i].tag
|
||||
}
|
||||
return byIndex(x).Less(i, j)
|
||||
}
|
||||
|
||||
// byIndex sorts field by index sequence.
|
||||
type byIndex []field
|
||||
|
||||
func (x byIndex) Len() int { return len(x) }
|
||||
|
||||
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (x byIndex) Less(i, j int) bool {
|
||||
for k, xik := range x[i].index {
|
||||
if k >= len(x[j].index) {
|
||||
return false
|
||||
}
|
||||
if xik != x[j].index[k] {
|
||||
return xik < x[j].index[k]
|
||||
}
|
||||
}
|
||||
return len(x[i].index) < len(x[j].index)
|
||||
}
|
||||
|
||||
// typeFields returns a list of fields that TOML should recognize for the given
|
||||
// type. The algorithm is breadth-first search over the set of structs to
|
||||
// include - the top struct and then any reachable anonymous structs.
|
||||
func typeFields(t reflect.Type) []field {
|
||||
// Anonymous fields to explore at the current level and the next.
|
||||
current := []field{}
|
||||
next := []field{{typ: t}}
|
||||
|
||||
// Count of queued names for current level and the next.
|
||||
count := map[reflect.Type]int{}
|
||||
nextCount := map[reflect.Type]int{}
|
||||
|
||||
// Types already visited at an earlier level.
|
||||
visited := map[reflect.Type]bool{}
|
||||
|
||||
// Fields found.
|
||||
var fields []field
|
||||
|
||||
for len(next) > 0 {
|
||||
current, next = next, current[:0]
|
||||
count, nextCount = nextCount, map[reflect.Type]int{}
|
||||
|
||||
for _, f := range current {
|
||||
if visited[f.typ] {
|
||||
continue
|
||||
}
|
||||
visited[f.typ] = true
|
||||
|
||||
// Scan f.typ for fields to include.
|
||||
for i := 0; i < f.typ.NumField(); i++ {
|
||||
sf := f.typ.Field(i)
|
||||
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||
continue
|
||||
}
|
||||
opts := getOptions(sf.Tag)
|
||||
if opts.skip {
|
||||
continue
|
||||
}
|
||||
index := make([]int, len(f.index)+1)
|
||||
copy(index, f.index)
|
||||
index[len(f.index)] = i
|
||||
|
||||
ft := sf.Type
|
||||
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
|
||||
// Follow pointer.
|
||||
ft = ft.Elem()
|
||||
}
|
||||
|
||||
// Record found field and index sequence.
|
||||
if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
|
||||
tagged := opts.name != ""
|
||||
name := opts.name
|
||||
if name == "" {
|
||||
name = sf.Name
|
||||
}
|
||||
fields = append(fields, field{name, tagged, index, ft})
|
||||
if count[f.typ] > 1 {
|
||||
// If there were multiple instances, add a second,
|
||||
// so that the annihilation code will see a duplicate.
|
||||
// It only cares about the distinction between 1 or 2,
|
||||
// so don't bother generating any more copies.
|
||||
fields = append(fields, fields[len(fields)-1])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Record new anonymous struct to explore in next round.
|
||||
nextCount[ft]++
|
||||
if nextCount[ft] == 1 {
|
||||
f := field{name: ft.Name(), index: index, typ: ft}
|
||||
next = append(next, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(byName(fields))
|
||||
|
||||
// Delete all fields that are hidden by the Go rules for embedded fields,
|
||||
// except that fields with TOML tags are promoted.
|
||||
|
||||
// The fields are sorted in primary order of name, secondary order
|
||||
// of field index length. Loop over names; for each name, delete
|
||||
// hidden fields by choosing the one dominant field that survives.
|
||||
out := fields[:0]
|
||||
for advance, i := 0, 0; i < len(fields); i += advance {
|
||||
// One iteration per name.
|
||||
// Find the sequence of fields with the name of this first field.
|
||||
fi := fields[i]
|
||||
name := fi.name
|
||||
for advance = 1; i+advance < len(fields); advance++ {
|
||||
fj := fields[i+advance]
|
||||
if fj.name != name {
|
||||
break
|
||||
}
|
||||
}
|
||||
if advance == 1 { // Only one field with this name
|
||||
out = append(out, fi)
|
||||
continue
|
||||
}
|
||||
dominant, ok := dominantField(fields[i : i+advance])
|
||||
if ok {
|
||||
out = append(out, dominant)
|
||||
}
|
||||
}
|
||||
|
||||
fields = out
|
||||
sort.Sort(byIndex(fields))
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// dominantField looks through the fields, all of which are known to
|
||||
// have the same name, to find the single field that dominates the
|
||||
// others using Go's embedding rules, modified by the presence of
|
||||
// TOML tags. If there are multiple top-level fields, the boolean
|
||||
// will be false: This condition is an error in Go and we skip all
|
||||
// the fields.
|
||||
func dominantField(fields []field) (field, bool) {
|
||||
// The fields are sorted in increasing index-length order. The winner
|
||||
// must therefore be one with the shortest index length. Drop all
|
||||
// longer entries, which is easy: just truncate the slice.
|
||||
length := len(fields[0].index)
|
||||
tagged := -1 // Index of first tagged field.
|
||||
for i, f := range fields {
|
||||
if len(f.index) > length {
|
||||
fields = fields[:i]
|
||||
break
|
||||
}
|
||||
if f.tag {
|
||||
if tagged >= 0 {
|
||||
// Multiple tagged fields at the same level: conflict.
|
||||
// Return no field.
|
||||
return field{}, false
|
||||
}
|
||||
tagged = i
|
||||
}
|
||||
}
|
||||
if tagged >= 0 {
|
||||
return fields[tagged], true
|
||||
}
|
||||
// All remaining fields have the same length. If there's more than one,
|
||||
// we have a conflict (two fields named "X" at the same level) and we
|
||||
// return no field.
|
||||
if len(fields) > 1 {
|
||||
return field{}, false
|
||||
}
|
||||
return fields[0], true
|
||||
}
|
||||
|
||||
var fieldCache struct {
|
||||
sync.RWMutex
|
||||
m map[reflect.Type][]field
|
||||
}
|
||||
|
||||
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
||||
func cachedTypeFields(t reflect.Type) []field {
|
||||
fieldCache.RLock()
|
||||
f := fieldCache.m[t]
|
||||
fieldCache.RUnlock()
|
||||
if f != nil {
|
||||
return f
|
||||
}
|
||||
|
||||
// Compute fields without lock.
|
||||
// Might duplicate effort but won't hold other computations back.
|
||||
f = typeFields(t)
|
||||
if f == nil {
|
||||
f = []field{}
|
||||
}
|
||||
|
||||
fieldCache.Lock()
|
||||
if fieldCache.m == nil {
|
||||
fieldCache.m = map[reflect.Type][]field{}
|
||||
}
|
||||
fieldCache.m[t] = f
|
||||
fieldCache.Unlock()
|
||||
return f
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
|
@ -0,0 +1,43 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gobwas/glob"
|
||||
packages = [
|
||||
".",
|
||||
"compiler",
|
||||
"match",
|
||||
"syntax",
|
||||
"syntax/ast",
|
||||
"syntax/lexer",
|
||||
"util/runes",
|
||||
"util/strings"
|
||||
]
|
||||
revision = "5ccd90ef52e1e632236f7326478d4faa74f99438"
|
||||
version = "v0.2.3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/kisielk/gotool"
|
||||
packages = [
|
||||
".",
|
||||
"internal/load"
|
||||
]
|
||||
revision = "80517062f582ea3340cd4baf70e86d539ae7d84d"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/tools"
|
||||
packages = [
|
||||
"go/ast/astutil",
|
||||
"go/buildutil",
|
||||
"go/loader"
|
||||
]
|
||||
revision = "a5b4c53f6e8bdcafa95a94671bf2d1203365858b"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "7dd6ca0cba46360ae2d416534019ea1431850a15a69336f47a1098633d08e7b4"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
|
@ -0,0 +1,43 @@
|
|||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
ignored = ["github.com/davecgh/go-spew/spew"]
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/kisielk/gotool"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/tools"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gobwas/glob"
|
||||
version = "0.2.3"
|
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
|
@ -0,0 +1,73 @@
|
|||
# Depguard
|
||||
|
||||
Go linter that checks package imports are in a list of acceptable packages. It
|
||||
supports a white list and black list option and can do prefix or glob matching.
|
||||
This allows you to allow imports from a whole organization or only
|
||||
allow specific packages within a repository. It is recommended to use prefix
|
||||
matching as it is faster than glob matching. The fewer glob matches the better.
|
||||
|
||||
> If a pattern is matched by prefix it does not try to match via glob.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get -u github.com/OpenPeeDeeP/depguard
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
By default, Depguard looks for a file named `.depguard.json` in the current
|
||||
current working directory. If it is somewhere else, pass in the `-c` flag with
|
||||
the location of your configuration file.
|
||||
|
||||
The following is an example configuration file.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "whitelist",
|
||||
"packages": [
|
||||
"github.com/OpenPeeDeeP/depguard"
|
||||
],
|
||||
"includeGoRoot": true
|
||||
}
|
||||
```
|
||||
|
||||
- `type` can be either `whitelist` or `blacklist`. This check is case insensitive.
|
||||
If not specified the default is `blacklist`.
|
||||
- `packages` is a list of packages for the list type specified.
|
||||
- Set `includeGoRoot` to true if you want to check the list against standard lib.
|
||||
If not specified the default is false.
|
||||
|
||||
## Gometalinter
|
||||
|
||||
The binary installation of this linter can be used with
|
||||
[Gometalinter](github.com/alecthomas/gometalinter).
|
||||
|
||||
If you use a configuration file for Gometalinter then the following will need to
|
||||
be added to your configuration file.
|
||||
|
||||
```json
|
||||
{
|
||||
"linters": {
|
||||
"depguard": {
|
||||
"command": "depguard -c path/to/config.json",
|
||||
"pattern": "PATH:LINE:COL:MESSAGE",
|
||||
"installFrom": "github.com/OpenPeeDeeP/depguard",
|
||||
"isFast": true,
|
||||
"partitionStrategy": "packages"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you prefer the command line way the following will work for you as well.
|
||||
|
||||
```bash
|
||||
gometalinter --linter='depguard:depguard -c path/to/config.json:PATH:LINE:COL:MESSAGE'
|
||||
```
|
||||
|
||||
## Golangci-lint
|
||||
|
||||
This linter was built with
|
||||
[Golangci-lint](https://github.com/golangci/golangci-lint) in mind. It is compatable
|
||||
and read their docs to see how to implement all their linters, including this one.
|
|
@ -0,0 +1,185 @@
|
|||
package depguard
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"go/token"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"golang.org/x/tools/go/loader"
|
||||
)
|
||||
|
||||
//ListType states what kind of list is passed in.
|
||||
type ListType int
|
||||
|
||||
const (
|
||||
//LTBlacklist states the list given is a blacklist. (default)
|
||||
LTBlacklist ListType = iota
|
||||
//LTWhitelist states the list given is a whitelist.
|
||||
LTWhitelist
|
||||
)
|
||||
|
||||
//StringToListType makes it easier to turn a string into a ListType.
|
||||
//It assumes that the string representation is lower case.
|
||||
var StringToListType = map[string]ListType{
|
||||
"whitelist": LTWhitelist,
|
||||
"blacklist": LTBlacklist,
|
||||
}
|
||||
|
||||
//Issue with the package with PackageName at the Position.
|
||||
type Issue struct {
|
||||
PackageName string
|
||||
Position token.Position
|
||||
}
|
||||
|
||||
//Depguard checks imports to make sure they follow the given list and constraints.
|
||||
type Depguard struct {
|
||||
ListType ListType
|
||||
Packages []string
|
||||
IncludeGoRoot bool
|
||||
prefixPackages []string
|
||||
globPackages []glob.Glob
|
||||
buildCtx *build.Context
|
||||
cwd string
|
||||
}
|
||||
|
||||
//Run checks for dependencies given the program and validates them against
|
||||
//Packages.
|
||||
func (dg *Depguard) Run(config *loader.Config, prog *loader.Program) ([]*Issue, error) {
|
||||
//Shortcut execution on an empty blacklist as that means every package is allowed
|
||||
if dg.ListType == LTBlacklist && len(dg.Packages) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := dg.initialize(config, prog); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
directImports, err := dg.createImportMap(prog)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var issues []*Issue
|
||||
for pkg, positions := range directImports {
|
||||
if dg.flagIt(pkg) {
|
||||
for _, pos := range positions {
|
||||
issues = append(issues, &Issue{
|
||||
PackageName: pkg,
|
||||
Position: pos,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
func (dg *Depguard) initialize(config *loader.Config, prog *loader.Program) error {
|
||||
//Try and get the current working directory
|
||||
dg.cwd = config.Cwd
|
||||
if dg.cwd == "" {
|
||||
var err error
|
||||
dg.cwd, err = os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//Use the &build.Default if one is not specified
|
||||
dg.buildCtx = config.Build
|
||||
if dg.buildCtx == nil {
|
||||
dg.buildCtx = &build.Default
|
||||
}
|
||||
|
||||
for _, pkg := range dg.Packages {
|
||||
if strings.ContainsAny(pkg, "!?*[]{}") {
|
||||
g, err := glob.Compile(pkg, '/')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dg.globPackages = append(dg.globPackages, g)
|
||||
} else {
|
||||
dg.prefixPackages = append(dg.prefixPackages, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
//Sort the packages so we can have a faster search in the array
|
||||
sort.Strings(dg.prefixPackages)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dg *Depguard) createImportMap(prog *loader.Program) (map[string][]token.Position, error) {
|
||||
importMap := make(map[string][]token.Position)
|
||||
//For the directly imported packages
|
||||
for _, imported := range prog.InitialPackages() {
|
||||
//Go through their files
|
||||
for _, file := range imported.Files {
|
||||
//And populate a map of all direct imports and their positions
|
||||
//This will filter out GoRoot depending on the Depguard.IncludeGoRoot
|
||||
for _, fileImport := range file.Imports {
|
||||
fileImportPath := cleanBasicLitString(fileImport.Path.Value)
|
||||
if !dg.IncludeGoRoot {
|
||||
pkg, err := dg.buildCtx.Import(fileImportPath, dg.cwd, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pkg.Goroot {
|
||||
continue
|
||||
}
|
||||
}
|
||||
position := prog.Fset.Position(fileImport.Pos())
|
||||
positions, found := importMap[fileImportPath]
|
||||
if !found {
|
||||
importMap[fileImportPath] = []token.Position{
|
||||
position,
|
||||
}
|
||||
continue
|
||||
}
|
||||
importMap[fileImportPath] = append(positions, position)
|
||||
}
|
||||
}
|
||||
}
|
||||
return importMap, nil
|
||||
}
|
||||
|
||||
func (dg *Depguard) pkgInList(pkg string) bool {
|
||||
if dg.pkgInPrefixList(pkg) {
|
||||
return true
|
||||
}
|
||||
return dg.pkgInGlobList(pkg)
|
||||
}
|
||||
|
||||
func (dg *Depguard) pkgInPrefixList(pkg string) bool {
|
||||
//Idx represents where in the package slice the passed in package would go
|
||||
//when sorted. -1 Just means that it would be at the very front of the slice.
|
||||
idx := sort.Search(len(dg.prefixPackages), func(i int) bool {
|
||||
return dg.prefixPackages[i] > pkg
|
||||
}) - 1
|
||||
//This means that the package passed in has no way to be prefixed by anything
|
||||
//in the package list as it is already smaller then everything
|
||||
if idx == -1 {
|
||||
return false
|
||||
}
|
||||
return strings.HasPrefix(pkg, dg.prefixPackages[idx])
|
||||
}
|
||||
|
||||
func (dg *Depguard) pkgInGlobList(pkg string) bool {
|
||||
for _, g := range dg.globPackages {
|
||||
if g.Match(pkg) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//InList | WhiteList | BlackList
|
||||
// y | | x
|
||||
// n | x |
|
||||
func (dg *Depguard) flagIt(pkg string) bool {
|
||||
return dg.pkgInList(pkg) == (dg.ListType == LTBlacklist)
|
||||
}
|
||||
|
||||
func cleanBasicLitString(value string) string {
|
||||
return strings.Trim(value, "\"\\")
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.8.x
|
||||
- tip
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-colorable"
|
||||
packages = ["."]
|
||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
||||
version = "v0.0.9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "e8a50671c3cb93ea935bf210b1cd20702876b9d9226129be581ef646d1565cdc"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mattn/go-colorable"
|
||||
version = "0.0.9"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mattn/go-isatty"
|
||||
version = "0.0.3"
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Fatih Arslan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,179 @@
|
|||
# Color [](https://godoc.org/github.com/fatih/color) [](https://travis-ci.org/fatih/color)
|
||||
|
||||
|
||||
|
||||
Color lets you use colorized outputs in terms of [ANSI Escape
|
||||
Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It
|
||||
has support for Windows too! The API can be used in several ways, pick one that
|
||||
suits you.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/fatih/color
|
||||
```
|
||||
|
||||
Note that the `vendor` folder is here for stability. Remove the folder if you
|
||||
already have the dependencies in your GOPATH.
|
||||
|
||||
## Examples
|
||||
|
||||
### Standard colors
|
||||
|
||||
```go
|
||||
// Print with default helper functions
|
||||
color.Cyan("Prints text in cyan.")
|
||||
|
||||
// A newline will be appended automatically
|
||||
color.Blue("Prints %s in blue.", "text")
|
||||
|
||||
// These are using the default foreground colors
|
||||
color.Red("We have red")
|
||||
color.Magenta("And many others ..")
|
||||
|
||||
```
|
||||
|
||||
### Mix and reuse colors
|
||||
|
||||
```go
|
||||
// Create a new color object
|
||||
c := color.New(color.FgCyan).Add(color.Underline)
|
||||
c.Println("Prints cyan text with an underline.")
|
||||
|
||||
// Or just add them to New()
|
||||
d := color.New(color.FgCyan, color.Bold)
|
||||
d.Printf("This prints bold cyan %s\n", "too!.")
|
||||
|
||||
// Mix up foreground and background colors, create new mixes!
|
||||
red := color.New(color.FgRed)
|
||||
|
||||
boldRed := red.Add(color.Bold)
|
||||
boldRed.Println("This will print text in bold red.")
|
||||
|
||||
whiteBackground := red.Add(color.BgWhite)
|
||||
whiteBackground.Println("Red text with white background.")
|
||||
```
|
||||
|
||||
### Use your own output (io.Writer)
|
||||
|
||||
```go
|
||||
// Use your own io.Writer output
|
||||
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
|
||||
|
||||
blue := color.New(color.FgBlue)
|
||||
blue.Fprint(writer, "This will print text in blue.")
|
||||
```
|
||||
|
||||
### Custom print functions (PrintFunc)
|
||||
|
||||
```go
|
||||
// Create a custom print function for convenience
|
||||
red := color.New(color.FgRed).PrintfFunc()
|
||||
red("Warning")
|
||||
red("Error: %s", err)
|
||||
|
||||
// Mix up multiple attributes
|
||||
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
|
||||
notice("Don't forget this...")
|
||||
```
|
||||
|
||||
### Custom fprint functions (FprintFunc)
|
||||
|
||||
```go
|
||||
blue := color.New(FgBlue).FprintfFunc()
|
||||
blue(myWriter, "important notice: %s", stars)
|
||||
|
||||
// Mix up with multiple attributes
|
||||
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
|
||||
success(myWriter, "Don't forget this...")
|
||||
```
|
||||
|
||||
### Insert into noncolor strings (SprintFunc)
|
||||
|
||||
```go
|
||||
// Create SprintXxx functions to mix strings with other non-colorized strings:
|
||||
yellow := color.New(color.FgYellow).SprintFunc()
|
||||
red := color.New(color.FgRed).SprintFunc()
|
||||
fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error"))
|
||||
|
||||
info := color.New(color.FgWhite, color.BgGreen).SprintFunc()
|
||||
fmt.Printf("This %s rocks!\n", info("package"))
|
||||
|
||||
// Use helper functions
|
||||
fmt.Println("This", color.RedString("warning"), "should be not neglected.")
|
||||
fmt.Printf("%v %v\n", color.GreenString("Info:"), "an important message.")
|
||||
|
||||
// Windows supported too! Just don't forget to change the output to color.Output
|
||||
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
|
||||
```
|
||||
|
||||
### Plug into existing code
|
||||
|
||||
```go
|
||||
// Use handy standard colors
|
||||
color.Set(color.FgYellow)
|
||||
|
||||
fmt.Println("Existing text will now be in yellow")
|
||||
fmt.Printf("This one %s\n", "too")
|
||||
|
||||
color.Unset() // Don't forget to unset
|
||||
|
||||
// You can mix up parameters
|
||||
color.Set(color.FgMagenta, color.Bold)
|
||||
defer color.Unset() // Use it in your function
|
||||
|
||||
fmt.Println("All text will now be bold magenta.")
|
||||
```
|
||||
|
||||
### Disable/Enable color
|
||||
|
||||
There might be a case where you want to explicitly disable/enable color output. the
|
||||
`go-isatty` package will automatically disable color output for non-tty output streams
|
||||
(for example if the output were piped directly to `less`)
|
||||
|
||||
`Color` has support to disable/enable colors both globally and for single color
|
||||
definitions. For example suppose you have a CLI app and a `--no-color` bool flag. You
|
||||
can easily disable the color output with:
|
||||
|
||||
```go
|
||||
|
||||
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
|
||||
|
||||
if *flagNoColor {
|
||||
color.NoColor = true // disables colorized output
|
||||
}
|
||||
```
|
||||
|
||||
It also has support for single color definitions (local). You can
|
||||
disable/enable color output on the fly:
|
||||
|
||||
```go
|
||||
c := color.New(color.FgCyan)
|
||||
c.Println("Prints cyan text")
|
||||
|
||||
c.DisableColor()
|
||||
c.Println("This is printed without any color")
|
||||
|
||||
c.EnableColor()
|
||||
c.Println("This prints again cyan...")
|
||||
```
|
||||
|
||||
## Todo
|
||||
|
||||
* Save/Return previous values
|
||||
* Evaluate fmt.Formatter interface
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
* [Fatih Arslan](https://github.com/fatih)
|
||||
* Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable)
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details
|
||||
|
|
@ -0,0 +1,600 @@
|
|||
package color
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
var (
|
||||
// NoColor defines if the output is colorized or not. It's dynamically set to
|
||||
// false or true based on the stdout's file descriptor referring to a terminal
|
||||
// or not. This is a global option and affects all colors. For more control
|
||||
// over each color block use the methods DisableColor() individually.
|
||||
NoColor = os.Getenv("TERM") == "dumb" ||
|
||||
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
|
||||
|
||||
// Output defines the standard output of the print functions. By default
|
||||
// os.Stdout is used.
|
||||
Output = colorable.NewColorableStdout()
|
||||
|
||||
// colorsCache is used to reduce the count of created Color objects and
|
||||
// allows to reuse already created objects with required Attribute.
|
||||
colorsCache = make(map[Attribute]*Color)
|
||||
colorsCacheMu sync.Mutex // protects colorsCache
|
||||
)
|
||||
|
||||
// Color defines a custom color object which is defined by SGR parameters.
|
||||
type Color struct {
|
||||
params []Attribute
|
||||
noColor *bool
|
||||
}
|
||||
|
||||
// Attribute defines a single SGR Code
|
||||
type Attribute int
|
||||
|
||||
const escape = "\x1b"
|
||||
|
||||
// Base attributes
|
||||
const (
|
||||
Reset Attribute = iota
|
||||
Bold
|
||||
Faint
|
||||
Italic
|
||||
Underline
|
||||
BlinkSlow
|
||||
BlinkRapid
|
||||
ReverseVideo
|
||||
Concealed
|
||||
CrossedOut
|
||||
)
|
||||
|
||||
// Foreground text colors
|
||||
const (
|
||||
FgBlack Attribute = iota + 30
|
||||
FgRed
|
||||
FgGreen
|
||||
FgYellow
|
||||
FgBlue
|
||||
FgMagenta
|
||||
FgCyan
|
||||
FgWhite
|
||||
)
|
||||
|
||||
// Foreground Hi-Intensity text colors
|
||||
const (
|
||||
FgHiBlack Attribute = iota + 90
|
||||
FgHiRed
|
||||
FgHiGreen
|
||||
FgHiYellow
|
||||
FgHiBlue
|
||||
FgHiMagenta
|
||||
FgHiCyan
|
||||
FgHiWhite
|
||||
)
|
||||
|
||||
// Background text colors
|
||||
const (
|
||||
BgBlack Attribute = iota + 40
|
||||
BgRed
|
||||
BgGreen
|
||||
BgYellow
|
||||
BgBlue
|
||||
BgMagenta
|
||||
BgCyan
|
||||
BgWhite
|
||||
)
|
||||
|
||||
// Background Hi-Intensity text colors
|
||||
const (
|
||||
BgHiBlack Attribute = iota + 100
|
||||
BgHiRed
|
||||
BgHiGreen
|
||||
BgHiYellow
|
||||
BgHiBlue
|
||||
BgHiMagenta
|
||||
BgHiCyan
|
||||
BgHiWhite
|
||||
)
|
||||
|
||||
// New returns a newly created color object.
|
||||
func New(value ...Attribute) *Color {
|
||||
c := &Color{params: make([]Attribute, 0)}
|
||||
c.Add(value...)
|
||||
return c
|
||||
}
|
||||
|
||||
// Set sets the given parameters immediately. It will change the color of
|
||||
// output with the given SGR parameters until color.Unset() is called.
|
||||
func Set(p ...Attribute) *Color {
|
||||
c := New(p...)
|
||||
c.Set()
|
||||
return c
|
||||
}
|
||||
|
||||
// Unset resets all escape attributes and clears the output. Usually should
|
||||
// be called after Set().
|
||||
func Unset() {
|
||||
if NoColor {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(Output, "%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// Set sets the SGR sequence.
|
||||
func (c *Color) Set() *Color {
|
||||
if c.isNoColorSet() {
|
||||
return c
|
||||
}
|
||||
|
||||
fmt.Fprintf(Output, c.format())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) unset() {
|
||||
if c.isNoColorSet() {
|
||||
return
|
||||
}
|
||||
|
||||
Unset()
|
||||
}
|
||||
|
||||
func (c *Color) setWriter(w io.Writer) *Color {
|
||||
if c.isNoColorSet() {
|
||||
return c
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, c.format())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) unsetWriter(w io.Writer) {
|
||||
if c.isNoColorSet() {
|
||||
return
|
||||
}
|
||||
|
||||
if NoColor {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// Add is used to chain SGR parameters. Use as many as parameters to combine
|
||||
// and create custom color objects. Example: Add(color.FgRed, color.Underline).
|
||||
func (c *Color) Add(value ...Attribute) *Color {
|
||||
c.params = append(c.params, value...)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) prepend(value Attribute) {
|
||||
c.params = append(c.params, 0)
|
||||
copy(c.params[1:], c.params[0:])
|
||||
c.params[0] = value
|
||||
}
|
||||
|
||||
// Fprint formats using the default formats for its operands and writes to w.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprint(w, a...)
|
||||
}
|
||||
|
||||
// Print formats using the default formats for its operands and writes to
|
||||
// standard output. Spaces are added between operands when neither is a
|
||||
// string. It returns the number of bytes written and any write error
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Print(a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprint(Output, a...)
|
||||
}
|
||||
|
||||
// Fprintf formats according to a format specifier and writes to w.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprintf(w, format, a...)
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// This is the standard fmt.Printf() method wrapped with the given color.
|
||||
func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprintf(Output, format, a...)
|
||||
}
|
||||
|
||||
// Fprintln formats using the default formats for its operands and writes to w.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprintln(w, a...)
|
||||
}
|
||||
|
||||
// Println formats using the default formats for its operands and writes to
|
||||
// standard output. Spaces are always added between operands and a newline is
|
||||
// appended. It returns the number of bytes written and any write error
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Println(a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprintln(Output, a...)
|
||||
}
|
||||
|
||||
// Sprint is just like Print, but returns a string instead of printing it.
|
||||
func (c *Color) Sprint(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Sprintln is just like Println, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintln(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintln(a...))
|
||||
}
|
||||
|
||||
// Sprintf is just like Printf, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintf(format string, a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// FprintFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprint().
|
||||
func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) {
|
||||
return func(w io.Writer, a ...interface{}) {
|
||||
c.Fprint(w, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Print().
|
||||
func (c *Color) PrintFunc() func(a ...interface{}) {
|
||||
return func(a ...interface{}) {
|
||||
c.Print(a...)
|
||||
}
|
||||
}
|
||||
|
||||
// FprintfFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprintf().
|
||||
func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) {
|
||||
return func(w io.Writer, format string, a ...interface{}) {
|
||||
c.Fprintf(w, format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintfFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Printf().
|
||||
func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
|
||||
return func(format string, a ...interface{}) {
|
||||
c.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// FprintlnFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprintln().
|
||||
func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) {
|
||||
return func(w io.Writer, a ...interface{}) {
|
||||
c.Fprintln(w, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintlnFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Println().
|
||||
func (c *Color) PrintlnFunc() func(a ...interface{}) {
|
||||
return func(a ...interface{}) {
|
||||
c.Println(a...)
|
||||
}
|
||||
}
|
||||
|
||||
// SprintFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprint(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output, example:
|
||||
//
|
||||
// put := New(FgYellow).SprintFunc()
|
||||
// fmt.Fprintf(color.Output, "This is a %s", put("warning"))
|
||||
func (c *Color) SprintFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprint(a...))
|
||||
}
|
||||
}
|
||||
|
||||
// SprintfFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprintf(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
|
||||
return func(format string, a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintf(format, a...))
|
||||
}
|
||||
}
|
||||
|
||||
// SprintlnFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprintln(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintln(a...))
|
||||
}
|
||||
}
|
||||
|
||||
// sequence returns a formatted SGR sequence to be plugged into a "\x1b[...m"
|
||||
// an example output might be: "1;36" -> bold cyan
|
||||
func (c *Color) sequence() string {
|
||||
format := make([]string, len(c.params))
|
||||
for i, v := range c.params {
|
||||
format[i] = strconv.Itoa(int(v))
|
||||
}
|
||||
|
||||
return strings.Join(format, ";")
|
||||
}
|
||||
|
||||
// wrap wraps the s string with the colors attributes. The string is ready to
|
||||
// be printed.
|
||||
func (c *Color) wrap(s string) string {
|
||||
if c.isNoColorSet() {
|
||||
return s
|
||||
}
|
||||
|
||||
return c.format() + s + c.unformat()
|
||||
}
|
||||
|
||||
func (c *Color) format() string {
|
||||
return fmt.Sprintf("%s[%sm", escape, c.sequence())
|
||||
}
|
||||
|
||||
func (c *Color) unformat() string {
|
||||
return fmt.Sprintf("%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// DisableColor disables the color output. Useful to not change any existing
|
||||
// code and still being able to output. Can be used for flags like
|
||||
// "--no-color". To enable back use EnableColor() method.
|
||||
func (c *Color) DisableColor() {
|
||||
c.noColor = boolPtr(true)
|
||||
}
|
||||
|
||||
// EnableColor enables the color output. Use it in conjunction with
|
||||
// DisableColor(). Otherwise this method has no side effects.
|
||||
func (c *Color) EnableColor() {
|
||||
c.noColor = boolPtr(false)
|
||||
}
|
||||
|
||||
func (c *Color) isNoColorSet() bool {
|
||||
// check first if we have user setted action
|
||||
if c.noColor != nil {
|
||||
return *c.noColor
|
||||
}
|
||||
|
||||
// if not return the global option, which is disabled by default
|
||||
return NoColor
|
||||
}
|
||||
|
||||
// Equals returns a boolean value indicating whether two colors are equal.
|
||||
func (c *Color) Equals(c2 *Color) bool {
|
||||
if len(c.params) != len(c2.params) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, attr := range c.params {
|
||||
if !c2.attrExists(attr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Color) attrExists(a Attribute) bool {
|
||||
for _, attr := range c.params {
|
||||
if attr == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func boolPtr(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
func getCachedColor(p Attribute) *Color {
|
||||
colorsCacheMu.Lock()
|
||||
defer colorsCacheMu.Unlock()
|
||||
|
||||
c, ok := colorsCache[p]
|
||||
if !ok {
|
||||
c = New(p)
|
||||
colorsCache[p] = c
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func colorPrint(format string, p Attribute, a ...interface{}) {
|
||||
c := getCachedColor(p)
|
||||
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format += "\n"
|
||||
}
|
||||
|
||||
if len(a) == 0 {
|
||||
c.Print(format)
|
||||
} else {
|
||||
c.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
func colorString(format string, p Attribute, a ...interface{}) string {
|
||||
c := getCachedColor(p)
|
||||
|
||||
if len(a) == 0 {
|
||||
return c.SprintFunc()(format)
|
||||
}
|
||||
|
||||
return c.SprintfFunc()(format, a...)
|
||||
}
|
||||
|
||||
// Black is a convenient helper function to print with black foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }
|
||||
|
||||
// Red is a convenient helper function to print with red foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }
|
||||
|
||||
// Green is a convenient helper function to print with green foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }
|
||||
|
||||
// Yellow is a convenient helper function to print with yellow foreground.
|
||||
// A newline is appended to format by default.
|
||||
func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }
|
||||
|
||||
// Blue is a convenient helper function to print with blue foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }
|
||||
|
||||
// Magenta is a convenient helper function to print with magenta foreground.
|
||||
// A newline is appended to format by default.
|
||||
func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }
|
||||
|
||||
// Cyan is a convenient helper function to print with cyan foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }
|
||||
|
||||
// White is a convenient helper function to print with white foreground. A
|
||||
// newline is appended to format by default.
|
||||
func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }
|
||||
|
||||
// BlackString is a convenient helper function to return a string with black
|
||||
// foreground.
|
||||
func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }
|
||||
|
||||
// RedString is a convenient helper function to return a string with red
|
||||
// foreground.
|
||||
func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }
|
||||
|
||||
// GreenString is a convenient helper function to return a string with green
|
||||
// foreground.
|
||||
func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }
|
||||
|
||||
// YellowString is a convenient helper function to return a string with yellow
|
||||
// foreground.
|
||||
func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }
|
||||
|
||||
// BlueString is a convenient helper function to return a string with blue
|
||||
// foreground.
|
||||
func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }
|
||||
|
||||
// MagentaString is a convenient helper function to return a string with magenta
|
||||
// foreground.
|
||||
func MagentaString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgMagenta, a...)
|
||||
}
|
||||
|
||||
// CyanString is a convenient helper function to return a string with cyan
|
||||
// foreground.
|
||||
func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }
|
||||
|
||||
// WhiteString is a convenient helper function to return a string with white
|
||||
// foreground.
|
||||
func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
|
||||
|
||||
// HiBlack is a convenient helper function to print with hi-intensity black foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) }
|
||||
|
||||
// HiRed is a convenient helper function to print with hi-intensity red foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) }
|
||||
|
||||
// HiGreen is a convenient helper function to print with hi-intensity green foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) }
|
||||
|
||||
// HiYellow is a convenient helper function to print with hi-intensity yellow foreground.
|
||||
// A newline is appended to format by default.
|
||||
func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) }
|
||||
|
||||
// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) }
|
||||
|
||||
// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground.
|
||||
// A newline is appended to format by default.
|
||||
func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) }
|
||||
|
||||
// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) }
|
||||
|
||||
// HiWhite is a convenient helper function to print with hi-intensity white foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) }
|
||||
|
||||
// HiBlackString is a convenient helper function to return a string with hi-intensity black
|
||||
// foreground.
|
||||
func HiBlackString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiBlack, a...)
|
||||
}
|
||||
|
||||
// HiRedString is a convenient helper function to return a string with hi-intensity red
|
||||
// foreground.
|
||||
func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) }
|
||||
|
||||
// HiGreenString is a convenient helper function to return a string with hi-intensity green
|
||||
// foreground.
|
||||
func HiGreenString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiGreen, a...)
|
||||
}
|
||||
|
||||
// HiYellowString is a convenient helper function to return a string with hi-intensity yellow
|
||||
// foreground.
|
||||
func HiYellowString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiYellow, a...)
|
||||
}
|
||||
|
||||
// HiBlueString is a convenient helper function to return a string with hi-intensity blue
|
||||
// foreground.
|
||||
func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) }
|
||||
|
||||
// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta
|
||||
// foreground.
|
||||
func HiMagentaString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiMagenta, a...)
|
||||
}
|
||||
|
||||
// HiCyanString is a convenient helper function to return a string with hi-intensity cyan
|
||||
// foreground.
|
||||
func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) }
|
||||
|
||||
// HiWhiteString is a convenient helper function to return a string with hi-intensity white
|
||||
// foreground.
|
||||
func HiWhiteString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiWhite, a...)
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
Package color is an ANSI color package to output colorized or SGR defined
|
||||
output to the standard output. The API can be used in several way, pick one
|
||||
that suits you.
|
||||
|
||||
Use simple and default helper functions with predefined foreground colors:
|
||||
|
||||
color.Cyan("Prints text in cyan.")
|
||||
|
||||
// a newline will be appended automatically
|
||||
color.Blue("Prints %s in blue.", "text")
|
||||
|
||||
// More default foreground colors..
|
||||
color.Red("We have red")
|
||||
color.Yellow("Yellow color too!")
|
||||
color.Magenta("And many others ..")
|
||||
|
||||
// Hi-intensity colors
|
||||
color.HiGreen("Bright green color.")
|
||||
color.HiBlack("Bright black means gray..")
|
||||
color.HiWhite("Shiny white color!")
|
||||
|
||||
However there are times where custom color mixes are required. Below are some
|
||||
examples to create custom color objects and use the print functions of each
|
||||
separate color object.
|
||||
|
||||
// Create a new color object
|
||||
c := color.New(color.FgCyan).Add(color.Underline)
|
||||
c.Println("Prints cyan text with an underline.")
|
||||
|
||||
// Or just add them to New()
|
||||
d := color.New(color.FgCyan, color.Bold)
|
||||
d.Printf("This prints bold cyan %s\n", "too!.")
|
||||
|
||||
|
||||
// Mix up foreground and background colors, create new mixes!
|
||||
red := color.New(color.FgRed)
|
||||
|
||||
boldRed := red.Add(color.Bold)
|
||||
boldRed.Println("This will print text in bold red.")
|
||||
|
||||
whiteBackground := red.Add(color.BgWhite)
|
||||
whiteBackground.Println("Red text with White background.")
|
||||
|
||||
// Use your own io.Writer output
|
||||
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
|
||||
|
||||
blue := color.New(color.FgBlue)
|
||||
blue.Fprint(myWriter, "This will print text in blue.")
|
||||
|
||||
You can create PrintXxx functions to simplify even more:
|
||||
|
||||
// Create a custom print function for convenient
|
||||
red := color.New(color.FgRed).PrintfFunc()
|
||||
red("warning")
|
||||
red("error: %s", err)
|
||||
|
||||
// Mix up multiple attributes
|
||||
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
|
||||
notice("don't forget this...")
|
||||
|
||||
You can also FprintXxx functions to pass your own io.Writer:
|
||||
|
||||
blue := color.New(FgBlue).FprintfFunc()
|
||||
blue(myWriter, "important notice: %s", stars)
|
||||
|
||||
// Mix up with multiple attributes
|
||||
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
|
||||
success(myWriter, don't forget this...")
|
||||
|
||||
|
||||
Or create SprintXxx functions to mix strings with other non-colorized strings:
|
||||
|
||||
yellow := New(FgYellow).SprintFunc()
|
||||
red := New(FgRed).SprintFunc()
|
||||
|
||||
fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error"))
|
||||
|
||||
info := New(FgWhite, BgGreen).SprintFunc()
|
||||
fmt.Printf("this %s rocks!\n", info("package"))
|
||||
|
||||
Windows support is enabled by default. All Print functions work as intended.
|
||||
However only for color.SprintXXX functions, user should use fmt.FprintXXX and
|
||||
set the output to color.Output:
|
||||
|
||||
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
|
||||
|
||||
info := New(FgWhite, BgGreen).SprintFunc()
|
||||
fmt.Fprintf(color.Output, "this %s rocks!\n", info("package"))
|
||||
|
||||
Using with existing code is possible. Just use the Set() method to set the
|
||||
standard output to the given parameters. That way a rewrite of an existing
|
||||
code is not required.
|
||||
|
||||
// Use handy standard colors.
|
||||
color.Set(color.FgYellow)
|
||||
|
||||
fmt.Println("Existing text will be now in Yellow")
|
||||
fmt.Printf("This one %s\n", "too")
|
||||
|
||||
color.Unset() // don't forget to unset
|
||||
|
||||
// You can mix up parameters
|
||||
color.Set(color.FgMagenta, color.Bold)
|
||||
defer color.Unset() // use it in your function
|
||||
|
||||
fmt.Println("All text will be now bold magenta.")
|
||||
|
||||
There might be a case where you want to disable color output (for example to
|
||||
pipe the standard output of your app to somewhere else). `Color` has support to
|
||||
disable colors both globally and for single color definition. For example
|
||||
suppose you have a CLI app and a `--no-color` bool flag. You can easily disable
|
||||
the color output with:
|
||||
|
||||
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
|
||||
|
||||
if *flagNoColor {
|
||||
color.NoColor = true // disables colorized output
|
||||
}
|
||||
|
||||
It also has support for single color definitions (local). You can
|
||||
disable/enable color output on the fly:
|
||||
|
||||
c := color.New(color.FgCyan)
|
||||
c.Println("Prints cyan text")
|
||||
|
||||
c.DisableColor()
|
||||
c.Println("This is printed without any color")
|
||||
|
||||
c.EnableColor()
|
||||
c.Println("This prints again cyan...")
|
||||
*/
|
||||
package color
|
|
@ -0,0 +1,5 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
|
@ -0,0 +1,6 @@
|
|||
# Setup a Global .gitignore for OS and editor generated files:
|
||||
# https://help.github.com/articles/ignoring-files
|
||||
# git config --global core.excludesfile ~/.gitignore_global
|
||||
|
||||
.vagrant
|
||||
*.sublime-project
|
|
@ -0,0 +1,30 @@
|
|||
sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- tip
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
fast_finish: true
|
||||
|
||||
before_script:
|
||||
- go get -u github.com/golang/lint/golint
|
||||
|
||||
script:
|
||||
- go test -v --race ./...
|
||||
|
||||
after_script:
|
||||
- test -z "$(gofmt -s -l -w . | tee /dev/stderr)"
|
||||
- test -z "$(golint ./... | tee /dev/stderr)"
|
||||
- go vet ./...
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
notifications:
|
||||
email: false
|
|
@ -0,0 +1,52 @@
|
|||
# Names should be added to this file as
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
# You can update this list using the following command:
|
||||
#
|
||||
# $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Aaron L <aaron@bettercoder.net>
|
||||
Adrien Bustany <adrien@bustany.org>
|
||||
Amit Krishnan <amit.krishnan@oracle.com>
|
||||
Anmol Sethi <me@anmol.io>
|
||||
Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
|
||||
Bruno Bigras <bigras.bruno@gmail.com>
|
||||
Caleb Spare <cespare@gmail.com>
|
||||
Case Nelson <case@teammating.com>
|
||||
Chris Howey <chris@howey.me> <howeyc@gmail.com>
|
||||
Christoffer Buchholz <christoffer.buchholz@gmail.com>
|
||||
Daniel Wagner-Hall <dawagner@gmail.com>
|
||||
Dave Cheney <dave@cheney.net>
|
||||
Evan Phoenix <evan@fallingsnow.net>
|
||||
Francisco Souza <f@souza.cc>
|
||||
Hari haran <hariharan.uno@gmail.com>
|
||||
John C Barstow
|
||||
Kelvin Fo <vmirage@gmail.com>
|
||||
Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
|
||||
Matt Layher <mdlayher@gmail.com>
|
||||
Nathan Youngman <git@nathany.com>
|
||||
Nickolai Zeldovich <nickolai@csail.mit.edu>
|
||||
Patrick <patrick@dropbox.com>
|
||||
Paul Hammond <paul@paulhammond.org>
|
||||
Pawel Knap <pawelknap88@gmail.com>
|
||||
Pieter Droogendijk <pieter@binky.org.uk>
|
||||
Pursuit92 <JoshChase@techpursuit.net>
|
||||
Riku Voipio <riku.voipio@linaro.org>
|
||||
Rob Figueiredo <robfig@gmail.com>
|
||||
Rodrigo Chiossi <rodrigochiossi@gmail.com>
|
||||
Slawek Ligus <root@ooz.ie>
|
||||
Soge Zhang <zhssoge@gmail.com>
|
||||
Tiffany Jernigan <tiffany.jernigan@intel.com>
|
||||
Tilak Sharma <tilaks@google.com>
|
||||
Tom Payne <twpayne@gmail.com>
|
||||
Travis Cline <travis.cline@gmail.com>
|
||||
Tudor Golubenco <tudor.g@gmail.com>
|
||||
Vahe Khachikyan <vahe@live.ca>
|
||||
Yukang <moorekang@gmail.com>
|
||||
bronze1man <bronze1man@gmail.com>
|
||||
debrando <denis.brandolini@gmail.com>
|
||||
henrikedwards <henrik.edwards@gmail.com>
|
||||
铁哥 <guotie.9@gmail.com>
|
|
@ -0,0 +1,317 @@
|
|||
# Changelog
|
||||
|
||||
## v1.4.7 / 2018-01-09
|
||||
|
||||
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
||||
* Tests: Fix missing verb on format string (thanks @rchiossi)
|
||||
* Linux: Fix deadlock in Remove (thanks @aarondl)
|
||||
* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne)
|
||||
* Docs: Moved FAQ into the README (thanks @vahe)
|
||||
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
|
||||
* Docs: replace references to OS X with macOS
|
||||
|
||||
## v1.4.2 / 2016-10-10
|
||||
|
||||
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
||||
|
||||
## v1.4.1 / 2016-10-04
|
||||
|
||||
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
||||
|
||||
## v1.4.0 / 2016-10-01
|
||||
|
||||
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
||||
|
||||
## v1.3.1 / 2016-06-28
|
||||
|
||||
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
||||
|
||||
## v1.3.0 / 2016-04-19
|
||||
|
||||
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
||||
|
||||
## v1.2.10 / 2016-03-02
|
||||
|
||||
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
||||
|
||||
## v1.2.9 / 2016-01-13
|
||||
|
||||
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
||||
|
||||
## v1.2.8 / 2015-12-17
|
||||
|
||||
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
||||
* inotify: fix race in test
|
||||
* enable race detection for continuous integration (Linux, Mac, Windows)
|
||||
|
||||
## v1.2.5 / 2015-10-17
|
||||
|
||||
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
||||
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
||||
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
||||
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
||||
|
||||
## v1.2.1 / 2015-10-14
|
||||
|
||||
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
||||
|
||||
## v1.2.0 / 2015-02-08
|
||||
|
||||
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
||||
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
||||
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
||||
|
||||
## v1.1.1 / 2015-02-05
|
||||
|
||||
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
||||
|
||||
## v1.1.0 / 2014-12-12
|
||||
|
||||
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
||||
* add low-level functions
|
||||
* only need to store flags on directories
|
||||
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
|
||||
* done can be an unbuffered channel
|
||||
* remove calls to os.NewSyscallError
|
||||
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
|
||||
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
||||
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||
|
||||
## v1.0.4 / 2014-09-07
|
||||
|
||||
* kqueue: add dragonfly to the build tags.
|
||||
* Rename source code files, rearrange code so exported APIs are at the top.
|
||||
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
||||
|
||||
## v1.0.3 / 2014-08-19
|
||||
|
||||
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
||||
|
||||
## v1.0.2 / 2014-08-17
|
||||
|
||||
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
||||
|
||||
## v1.0.0 / 2014-08-15
|
||||
|
||||
* [API] Remove AddWatch on Windows, use Add.
|
||||
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
||||
* Minor updates based on feedback from golint.
|
||||
|
||||
## dev / 2014-07-09
|
||||
|
||||
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
|
||||
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
|
||||
|
||||
## dev / 2014-07-04
|
||||
|
||||
* kqueue: fix incorrect mutex used in Close()
|
||||
* Update example to demonstrate usage of Op.
|
||||
|
||||
## dev / 2014-06-28
|
||||
|
||||
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
|
||||
* Fix for String() method on Event (thanks Alex Brainman)
|
||||
* Don't build on Plan 9 or Solaris (thanks @4ad)
|
||||
|
||||
## dev / 2014-06-21
|
||||
|
||||
* Events channel of type Event rather than *Event.
|
||||
* [internal] use syscall constants directly for inotify and kqueue.
|
||||
* [internal] kqueue: rename events to kevents and fileEvent to event.
|
||||
|
||||
## dev / 2014-06-19
|
||||
|
||||
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
|
||||
* [internal] remove cookie from Event struct (unused).
|
||||
* [internal] Event struct has the same definition across every OS.
|
||||
* [internal] remove internal watch and removeWatch methods.
|
||||
|
||||
## dev / 2014-06-12
|
||||
|
||||
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
|
||||
* [API] Pluralized channel names: Events and Errors.
|
||||
* [API] Renamed FileEvent struct to Event.
|
||||
* [API] Op constants replace methods like IsCreate().
|
||||
|
||||
## dev / 2014-06-12
|
||||
|
||||
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||
|
||||
## dev / 2014-05-23
|
||||
|
||||
* [API] Remove current implementation of WatchFlags.
|
||||
* current implementation doesn't take advantage of OS for efficiency
|
||||
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
|
||||
* no tests for the current implementation
|
||||
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
||||
|
||||
## v0.9.3 / 2014-12-31
|
||||
|
||||
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||
|
||||
## v0.9.2 / 2014-08-17
|
||||
|
||||
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||
|
||||
## v0.9.1 / 2014-06-12
|
||||
|
||||
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||
|
||||
## v0.9.0 / 2014-01-17
|
||||
|
||||
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
||||
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
||||
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
||||
|
||||
## v0.8.12 / 2013-11-13
|
||||
|
||||
* [API] Remove FD_SET and friends from Linux adapter
|
||||
|
||||
## v0.8.11 / 2013-11-02
|
||||
|
||||
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
||||
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
|
||||
|
||||
## v0.8.10 / 2013-10-19
|
||||
|
||||
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
||||
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
||||
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
||||
|
||||
## v0.8.9 / 2013-09-08
|
||||
|
||||
* [Doc] Contributing (thanks @nathany)
|
||||
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
||||
* [Doc] GoCI badge in README (Linux only) [#60][]
|
||||
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
||||
|
||||
## v0.8.8 / 2013-06-17
|
||||
|
||||
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
||||
|
||||
## v0.8.7 / 2013-06-03
|
||||
|
||||
* [API] Make syscall flags internal
|
||||
* [Fix] inotify: ignore event changes
|
||||
* [Fix] race in symlink test [#45][] (reported by @srid)
|
||||
* [Fix] tests on Windows
|
||||
* lower case error messages
|
||||
|
||||
## v0.8.6 / 2013-05-23
|
||||
|
||||
* kqueue: Use EVT_ONLY flag on Darwin
|
||||
* [Doc] Update README with full example
|
||||
|
||||
## v0.8.5 / 2013-05-09
|
||||
|
||||
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
||||
|
||||
## v0.8.4 / 2013-04-07
|
||||
|
||||
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
||||
|
||||
## v0.8.3 / 2013-03-13
|
||||
|
||||
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
||||
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
||||
|
||||
## v0.8.2 / 2013-02-07
|
||||
|
||||
* [Doc] add Authors
|
||||
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
||||
|
||||
## v0.8.1 / 2013-01-09
|
||||
|
||||
* [Fix] Windows path separators
|
||||
* [Doc] BSD License
|
||||
|
||||
## v0.8.0 / 2012-11-09
|
||||
|
||||
* kqueue: directory watching improvements (thanks @vmirage)
|
||||
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
||||
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
||||
|
||||
## v0.7.4 / 2012-10-09
|
||||
|
||||
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
||||
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
||||
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
||||
* [Fix] kqueue: modify after recreation of file
|
||||
|
||||
## v0.7.3 / 2012-09-27
|
||||
|
||||
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
||||
* [Fix] kqueue: no longer get duplicate CREATE events
|
||||
|
||||
## v0.7.2 / 2012-09-01
|
||||
|
||||
* kqueue: events for created directories
|
||||
|
||||
## v0.7.1 / 2012-07-14
|
||||
|
||||
* [Fix] for renaming files
|
||||
|
||||
## v0.7.0 / 2012-07-02
|
||||
|
||||
* [Feature] FSNotify flags
|
||||
* [Fix] inotify: Added file name back to event path
|
||||
|
||||
## v0.6.0 / 2012-06-06
|
||||
|
||||
* kqueue: watch files after directory created (thanks @tmc)
|
||||
|
||||
## v0.5.1 / 2012-05-22
|
||||
|
||||
* [Fix] inotify: remove all watches before Close()
|
||||
|
||||
## v0.5.0 / 2012-05-03
|
||||
|
||||
* [API] kqueue: return errors during watch instead of sending over channel
|
||||
* kqueue: match symlink behavior on Linux
|
||||
* inotify: add `DELETE_SELF` (requested by @taralx)
|
||||
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
||||
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
||||
|
||||
## v0.4.0 / 2012-03-30
|
||||
|
||||
* Go 1 released: build with go tool
|
||||
* [Feature] Windows support using winfsnotify
|
||||
* Windows does not have attribute change notifications
|
||||
* Roll attribute notifications into IsModify
|
||||
|
||||
## v0.3.0 / 2012-02-19
|
||||
|
||||
* kqueue: add files when watch directory
|
||||
|
||||
## v0.2.0 / 2011-12-30
|
||||
|
||||
* update to latest Go weekly code
|
||||
|
||||
## v0.1.0 / 2011-10-19
|
||||
|
||||
* kqueue: add watch on file creation to match inotify
|
||||
* kqueue: create file event
|
||||
* inotify: ignore `IN_IGNORED` events
|
||||
* event String()
|
||||
* linux: common FileEvent functions
|
||||
* initial commit
|
||||
|
||||
[#79]: https://github.com/howeyc/fsnotify/pull/79
|
||||
[#77]: https://github.com/howeyc/fsnotify/pull/77
|
||||
[#72]: https://github.com/howeyc/fsnotify/issues/72
|
||||
[#71]: https://github.com/howeyc/fsnotify/issues/71
|
||||
[#70]: https://github.com/howeyc/fsnotify/issues/70
|
||||
[#63]: https://github.com/howeyc/fsnotify/issues/63
|
||||
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||
[#60]: https://github.com/howeyc/fsnotify/issues/60
|
||||
[#59]: https://github.com/howeyc/fsnotify/issues/59
|
||||
[#49]: https://github.com/howeyc/fsnotify/issues/49
|
||||
[#45]: https://github.com/howeyc/fsnotify/issues/45
|
||||
[#40]: https://github.com/howeyc/fsnotify/issues/40
|
||||
[#36]: https://github.com/howeyc/fsnotify/issues/36
|
||||
[#33]: https://github.com/howeyc/fsnotify/issues/33
|
||||
[#29]: https://github.com/howeyc/fsnotify/issues/29
|
||||
[#25]: https://github.com/howeyc/fsnotify/issues/25
|
||||
[#24]: https://github.com/howeyc/fsnotify/issues/24
|
||||
[#21]: https://github.com/howeyc/fsnotify/issues/21
|
|
@ -0,0 +1,77 @@
|
|||
# Contributing
|
||||
|
||||
## Issues
|
||||
|
||||
* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues).
|
||||
* Please indicate the platform you are using fsnotify on.
|
||||
* A code example to reproduce the problem is appreciated.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
### Contributor License Agreement
|
||||
|
||||
fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
|
||||
|
||||
Please indicate that you have signed the CLA in your pull request.
|
||||
|
||||
### How fsnotify is Developed
|
||||
|
||||
* Development is done on feature branches.
|
||||
* Tests are run on BSD, Linux, macOS and Windows.
|
||||
* Pull requests are reviewed and [applied to master][am] using [hub][].
|
||||
* Maintainers may modify or squash commits rather than asking contributors to.
|
||||
* To issue a new release, the maintainers will:
|
||||
* Update the CHANGELOG
|
||||
* Tag a version, which will become available through gopkg.in.
|
||||
|
||||
### How to Fork
|
||||
|
||||
For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
|
||||
|
||||
1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`)
|
||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
3. Ensure everything works and the tests pass (see below)
|
||||
4. Commit your changes (`git commit -am 'Add some feature'`)
|
||||
|
||||
Contribute upstream:
|
||||
|
||||
1. Fork fsnotify on GitHub
|
||||
2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
|
||||
3. Push to the branch (`git push fork my-new-feature`)
|
||||
4. Create a new Pull Request on GitHub
|
||||
|
||||
This workflow is [thoroughly explained by Katrina Owen](https://splice.com/blog/contributing-open-source-git-repositories-go/).
|
||||
|
||||
### Testing
|
||||
|
||||
fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Windows.
|
||||
|
||||
Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
|
||||
|
||||
To aid in cross-platform testing there is a Vagrantfile for Linux and BSD.
|
||||
|
||||
* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
|
||||
* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
|
||||
* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
|
||||
* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`.
|
||||
* When you're done, you will want to halt or destroy the Vagrant boxes.
|
||||
|
||||
Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory.
|
||||
|
||||
Right now there is no equivalent solution for Windows and macOS, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
|
||||
|
||||
### Maintainers
|
||||
|
||||
Help maintaining fsnotify is welcome. To be a maintainer:
|
||||
|
||||
* Submit a pull request and sign the CLA as above.
|
||||
* You must be able to run the test suite on Mac, Windows, Linux and BSD.
|
||||
|
||||
To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][].
|
||||
|
||||
All code changes should be internal pull requests.
|
||||
|
||||
Releases are tagged using [Semantic Versioning](http://semver.org/).
|
||||
|
||||
[hub]: https://github.com/github/hub
|
||||
[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs
|
|
@ -0,0 +1,28 @@
|
|||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2012 fsnotify 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.
|
|
@ -0,0 +1,79 @@
|
|||
# File system notifications for Go
|
||||
|
||||
[](https://godoc.org/github.com/fsnotify/fsnotify) [](https://goreportcard.com/report/github.com/fsnotify/fsnotify)
|
||||
|
||||
fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. Ensure you have the latest version installed by running:
|
||||
|
||||
```console
|
||||
go get -u golang.org/x/sys/...
|
||||
```
|
||||
|
||||
Cross platform: Windows, Linux, BSD and macOS.
|
||||
|
||||
|Adapter |OS |Status |
|
||||
|----------|----------|----------|
|
||||
|inotify |Linux 2.6.27 or later, Android\*|Supported [](https://travis-ci.org/fsnotify/fsnotify)|
|
||||
|kqueue |BSD, macOS, iOS\*|Supported [](https://travis-ci.org/fsnotify/fsnotify)|
|
||||
|ReadDirectoryChangesW|Windows|Supported [](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)|
|
||||
|FSEvents |macOS |[Planned](https://github.com/fsnotify/fsnotify/issues/11)|
|
||||
|FEN |Solaris 11 |[In Progress](https://github.com/fsnotify/fsnotify/issues/12)|
|
||||
|fanotify |Linux 2.6.37+ | |
|
||||
|USN Journals |Windows |[Maybe](https://github.com/fsnotify/fsnotify/issues/53)|
|
||||
|Polling |*All* |[Maybe](https://github.com/fsnotify/fsnotify/issues/9)|
|
||||
|
||||
\* Android and iOS are untested.
|
||||
|
||||
Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information.
|
||||
|
||||
## API stability
|
||||
|
||||
fsnotify is a fork of [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA).
|
||||
|
||||
All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). Further API changes are [planned](https://github.com/fsnotify/fsnotify/milestones), and will be tagged with a new major revision number.
|
||||
|
||||
Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please refer to [CONTRIBUTING][] before opening an issue or pull request.
|
||||
|
||||
## Example
|
||||
|
||||
See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go).
|
||||
|
||||
## FAQ
|
||||
|
||||
**When a file is moved to another directory is it still being watched?**
|
||||
|
||||
No (it shouldn't be, unless you are watching where it was moved to).
|
||||
|
||||
**When I watch a directory, are all subdirectories watched as well?**
|
||||
|
||||
No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]).
|
||||
|
||||
**Do I have to watch the Error and Event channels in a separate goroutine?**
|
||||
|
||||
As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7])
|
||||
|
||||
**Why am I receiving multiple events for the same file on OS X?**
|
||||
|
||||
Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]).
|
||||
|
||||
**How many files can be watched at once?**
|
||||
|
||||
There are OS-specific limits as to how many watches can be created:
|
||||
* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error.
|
||||
* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error.
|
||||
|
||||
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||
[#18]: https://github.com/fsnotify/fsnotify/issues/18
|
||||
[#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||
[#7]: https://github.com/howeyc/fsnotify/issues/7
|
||||
|
||||
[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md
|
||||
|
||||
## Related Projects
|
||||
|
||||
* [notify](https://github.com/rjeczalik/notify)
|
||||
* [fsevents](https://github.com/fsnotify/fsevents)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// 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.
|
||||
|
||||
// +build solaris
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct {
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
}
|
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove stops watching the the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// 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.
|
||||
|
||||
// +build !plan9
|
||||
|
||||
// Package fsnotify provides a platform-independent interface for file system notifications.
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Event represents a single file system notification.
|
||||
type Event struct {
|
||||
Name string // Relative path to the file or directory.
|
||||
Op Op // File operation that triggered the event.
|
||||
}
|
||||
|
||||
// Op describes a set of file operations.
|
||||
type Op uint32
|
||||
|
||||
// These are the generalized file operations that can trigger a notification.
|
||||
const (
|
||||
Create Op = 1 << iota
|
||||
Write
|
||||
Remove
|
||||
Rename
|
||||
Chmod
|
||||
)
|
||||
|
||||
func (op Op) String() string {
|
||||
// Use a buffer for efficient string concatenation
|
||||
var buffer bytes.Buffer
|
||||
|
||||
if op&Create == Create {
|
||||
buffer.WriteString("|CREATE")
|
||||
}
|
||||
if op&Remove == Remove {
|
||||
buffer.WriteString("|REMOVE")
|
||||
}
|
||||
if op&Write == Write {
|
||||
buffer.WriteString("|WRITE")
|
||||
}
|
||||
if op&Rename == Rename {
|
||||
buffer.WriteString("|RENAME")
|
||||
}
|
||||
if op&Chmod == Chmod {
|
||||
buffer.WriteString("|CHMOD")
|
||||
}
|
||||
if buffer.Len() == 0 {
|
||||
return ""
|
||||
}
|
||||
return buffer.String()[1:] // Strip leading pipe
|
||||
}
|
||||
|
||||
// String returns a string representation of the event in the form
|
||||
// "file: REMOVE|WRITE|..."
|
||||
func (e Event) String() string {
|
||||
return fmt.Sprintf("%q: %s", e.Name, e.Op.String())
|
||||
}
|
||||
|
||||
// Common errors that can be reported by a watcher
|
||||
var ErrEventOverflow = errors.New("fsnotify queue overflow")
|
|
@ -0,0 +1,337 @@
|
|||
// 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.
|
||||
|
||||
// +build linux
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct {
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
mu sync.Mutex // Map access
|
||||
fd int
|
||||
poller *fdPoller
|
||||
watches map[string]*watch // Map of inotify watches (key: path)
|
||||
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||
doneResp chan struct{} // Channel to respond to Close
|
||||
}
|
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
// Create inotify fd
|
||||
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC)
|
||||
if fd == -1 {
|
||||
return nil, errno
|
||||
}
|
||||
// Create epoll
|
||||
poller, err := newFdPoller(fd)
|
||||
if err != nil {
|
||||
unix.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
w := &Watcher{
|
||||
fd: fd,
|
||||
poller: poller,
|
||||
watches: make(map[string]*watch),
|
||||
paths: make(map[int]string),
|
||||
Events: make(chan Event),
|
||||
Errors: make(chan error),
|
||||
done: make(chan struct{}),
|
||||
doneResp: make(chan struct{}),
|
||||
}
|
||||
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (w *Watcher) isClosed() bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
if w.isClosed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send 'close' signal to goroutine, and set the Watcher to closed.
|
||||
close(w.done)
|
||||
|
||||
// Wake up goroutine
|
||||
w.poller.wake()
|
||||
|
||||
// Wait for goroutine to close
|
||||
<-w.doneResp
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error {
|
||||
name = filepath.Clean(name)
|
||||
if w.isClosed() {
|
||||
return errors.New("inotify instance already closed")
|
||||
}
|
||||
|
||||
const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
|
||||
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
|
||||
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||
|
||||
var flags uint32 = agnosticEvents
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
watchEntry := w.watches[name]
|
||||
if watchEntry != nil {
|
||||
flags |= watchEntry.flags | unix.IN_MASK_ADD
|
||||
}
|
||||
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
|
||||
if wd == -1 {
|
||||
return errno
|
||||
}
|
||||
|
||||
if watchEntry == nil {
|
||||
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
|
||||
w.paths[wd] = name
|
||||
} else {
|
||||
watchEntry.wd = uint32(wd)
|
||||
watchEntry.flags = flags
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove stops watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
name = filepath.Clean(name)
|
||||
|
||||
// Fetch the watch.
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
watch, ok := w.watches[name]
|
||||
|
||||
// Remove it from inotify.
|
||||
if !ok {
|
||||
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
|
||||
}
|
||||
|
||||
// We successfully removed the watch if InotifyRmWatch doesn't return an
|
||||
// error, we need to clean up our internal state to ensure it matches
|
||||
// inotify's kernel state.
|
||||
delete(w.paths, int(watch.wd))
|
||||
delete(w.watches, name)
|
||||
|
||||
// inotify_rm_watch will return EINVAL if the file has been deleted;
|
||||
// the inotify will already have been removed.
|
||||
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
|
||||
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
|
||||
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
|
||||
// by another thread and we have not received IN_IGNORE event.
|
||||
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
|
||||
if success == -1 {
|
||||
// TODO: Perhaps it's not helpful to return an error here in every case.
|
||||
// the only two possible errors are:
|
||||
// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
|
||||
// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
|
||||
// Watch descriptors are invalidated when they are removed explicitly or implicitly;
|
||||
// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
|
||||
return errno
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type watch struct {
|
||||
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||
}
|
||||
|
||||
// readEvents reads from the inotify file descriptor, converts the
|
||||
// received events into Event objects and sends them via the Events channel
|
||||
func (w *Watcher) readEvents() {
|
||||
var (
|
||||
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||
n int // Number of bytes read with read()
|
||||
errno error // Syscall errno
|
||||
ok bool // For poller.wait
|
||||
)
|
||||
|
||||
defer close(w.doneResp)
|
||||
defer close(w.Errors)
|
||||
defer close(w.Events)
|
||||
defer unix.Close(w.fd)
|
||||
defer w.poller.close()
|
||||
|
||||
for {
|
||||
// See if we have been closed.
|
||||
if w.isClosed() {
|
||||
return
|
||||
}
|
||||
|
||||
ok, errno = w.poller.wait()
|
||||
if errno != nil {
|
||||
select {
|
||||
case w.Errors <- errno:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
n, errno = unix.Read(w.fd, buf[:])
|
||||
// If a signal interrupted execution, see if we've been asked to close, and try again.
|
||||
// http://man7.org/linux/man-pages/man7/signal.7.html :
|
||||
// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
|
||||
if errno == unix.EINTR {
|
||||
continue
|
||||
}
|
||||
|
||||
// unix.Read might have been woken up by Close. If so, we're done.
|
||||
if w.isClosed() {
|
||||
return
|
||||
}
|
||||
|
||||
if n < unix.SizeofInotifyEvent {
|
||||
var err error
|
||||
if n == 0 {
|
||||
// If EOF is received. This should really never happen.
|
||||
err = io.EOF
|
||||
} else if n < 0 {
|
||||
// If an error occurred while reading.
|
||||
err = errno
|
||||
} else {
|
||||
// Read was too short.
|
||||
err = errors.New("notify: short read in readEvents()")
|
||||
}
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var offset uint32
|
||||
// We don't know how many events we just read into the buffer
|
||||
// While the offset points to at least one whole event...
|
||||
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||
// Point "raw" to the event in the buffer
|
||||
raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||
|
||||
mask := uint32(raw.Mask)
|
||||
nameLen := uint32(raw.Len)
|
||||
|
||||
if mask&unix.IN_Q_OVERFLOW != 0 {
|
||||
select {
|
||||
case w.Errors <- ErrEventOverflow:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If the event happened to the watched directory or the watched file, the kernel
|
||||
// doesn't append the filename to the event, but we would like to always fill the
|
||||
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
||||
// the "paths" map.
|
||||
w.mu.Lock()
|
||||
name, ok := w.paths[int(raw.Wd)]
|
||||
// IN_DELETE_SELF occurs when the file/directory being watched is removed.
|
||||
// This is a sign to clean up the maps, otherwise we are no longer in sync
|
||||
// with the inotify kernel state which has already deleted the watch
|
||||
// automatically.
|
||||
if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||
delete(w.paths, int(raw.Wd))
|
||||
delete(w.watches, name)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
if nameLen > 0 {
|
||||
// Point "bytes" at the first byte of the filename
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))
|
||||
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||
}
|
||||
|
||||
event := newEvent(name, mask)
|
||||
|
||||
// Send the events that are not ignored on the events channel
|
||||
if !event.ignoreLinux(mask) {
|
||||
select {
|
||||
case w.Events <- event:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the next event in the buffer
|
||||
offset += unix.SizeofInotifyEvent + nameLen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Certain types of events can be "ignored" and not sent over the Events
|
||||
// channel. Such as events marked ignore by the kernel, or MODIFY events
|
||||
// against files that do not exist.
|
||||
func (e *Event) ignoreLinux(mask uint32) bool {
|
||||
// Ignore anything the inotify API says to ignore
|
||||
if mask&unix.IN_IGNORED == unix.IN_IGNORED {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the event is not a DELETE or RENAME, the file must exist.
|
||||
// Otherwise the event is ignored.
|
||||
// *Note*: this was put in place because it was seen that a MODIFY
|
||||
// event was sent after the DELETE. This ignores that MODIFY and
|
||||
// assumes a DELETE will come or has come if the file doesn't exist.
|
||||
if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
|
||||
_, statErr := os.Lstat(e.Name)
|
||||
return os.IsNotExist(statErr)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// newEvent returns an platform-independent Event based on an inotify mask.
|
||||
func newEvent(name string, mask uint32) Event {
|
||||
e := Event{Name: name}
|
||||
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||
e.Op |= Create
|
||||
}
|
||||
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
||||
e.Op |= Remove
|
||||
}
|
||||
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
||||
e.Op |= Write
|
||||
}
|
||||
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||
e.Op |= Rename
|
||||
}
|
||||
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
||||
e.Op |= Chmod
|
||||
}
|
||||
return e
|
||||
}
|
187
tests/tools/vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
Normal file
187
tests/tools/vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
Normal file
|
@ -0,0 +1,187 @@
|
|||
// 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.
|
||||
|
||||
// +build linux
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type fdPoller struct {
|
||||
fd int // File descriptor (as returned by the inotify_init() syscall)
|
||||
epfd int // Epoll file descriptor
|
||||
pipe [2]int // Pipe for waking up
|
||||
}
|
||||
|
||||
func emptyPoller(fd int) *fdPoller {
|
||||
poller := new(fdPoller)
|
||||
poller.fd = fd
|
||||
poller.epfd = -1
|
||||
poller.pipe[0] = -1
|
||||
poller.pipe[1] = -1
|
||||
return poller
|
||||
}
|
||||
|
||||
// Create a new inotify poller.
|
||||
// This creates an inotify handler, and an epoll handler.
|
||||
func newFdPoller(fd int) (*fdPoller, error) {
|
||||
var errno error
|
||||
poller := emptyPoller(fd)
|
||||
defer func() {
|
||||
if errno != nil {
|
||||
poller.close()
|
||||
}
|
||||
}()
|
||||
poller.fd = fd
|
||||
|
||||
// Create epoll fd
|
||||
poller.epfd, errno = unix.EpollCreate1(0)
|
||||
if poller.epfd == -1 {
|
||||
return nil, errno
|
||||
}
|
||||
// Create pipe; pipe[0] is the read end, pipe[1] the write end.
|
||||
errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK)
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
// Register inotify fd with epoll
|
||||
event := unix.EpollEvent{
|
||||
Fd: int32(poller.fd),
|
||||
Events: unix.EPOLLIN,
|
||||
}
|
||||
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
// Register pipe fd with epoll
|
||||
event = unix.EpollEvent{
|
||||
Fd: int32(poller.pipe[0]),
|
||||
Events: unix.EPOLLIN,
|
||||
}
|
||||
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event)
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
return poller, nil
|
||||
}
|
||||
|
||||
// Wait using epoll.
|
||||
// Returns true if something is ready to be read,
|
||||
// false if there is not.
|
||||
func (poller *fdPoller) wait() (bool, error) {
|
||||
// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
|
||||
// I don't know whether epoll_wait returns the number of events returned,
|
||||
// or the total number of events ready.
|
||||
// I decided to catch both by making the buffer one larger than the maximum.
|
||||
events := make([]unix.EpollEvent, 7)
|
||||
for {
|
||||
n, errno := unix.EpollWait(poller.epfd, events, -1)
|
||||
if n == -1 {
|
||||
if errno == unix.EINTR {
|
||||
continue
|
||||
}
|
||||
return false, errno
|
||||
}
|
||||
if n == 0 {
|
||||
// If there are no events, try again.
|
||||
continue
|
||||
}
|
||||
if n > 6 {
|
||||
// This should never happen. More events were returned than should be possible.
|
||||
return false, errors.New("epoll_wait returned more events than I know what to do with")
|
||||
}
|
||||
ready := events[:n]
|
||||
epollhup := false
|
||||
epollerr := false
|
||||
epollin := false
|
||||
for _, event := range ready {
|
||||
if event.Fd == int32(poller.fd) {
|
||||
if event.Events&unix.EPOLLHUP != 0 {
|
||||
// This should not happen, but if it does, treat it as a wakeup.
|
||||
epollhup = true
|
||||
}
|
||||
if event.Events&unix.EPOLLERR != 0 {
|
||||
// If an error is waiting on the file descriptor, we should pretend
|
||||
// something is ready to read, and let unix.Read pick up the error.
|
||||
epollerr = true
|
||||
}
|
||||
if event.Events&unix.EPOLLIN != 0 {
|
||||
// There is data to read.
|
||||
epollin = true
|
||||
}
|
||||
}
|
||||
if event.Fd == int32(poller.pipe[0]) {
|
||||
if event.Events&unix.EPOLLHUP != 0 {
|
||||
// Write pipe descriptor was closed, by us. This means we're closing down the
|
||||
// watcher, and we should wake up.
|
||||
}
|
||||
if event.Events&unix.EPOLLERR != 0 {
|
||||
// If an error is waiting on the pipe file descriptor.
|
||||
// This is an absolute mystery, and should never ever happen.
|
||||
return false, errors.New("Error on the pipe descriptor.")
|
||||
}
|
||||
if event.Events&unix.EPOLLIN != 0 {
|
||||
// This is a regular wakeup, so we have to clear the buffer.
|
||||
err := poller.clearWake()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if epollhup || epollerr || epollin {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Close the write end of the poller.
|
||||
func (poller *fdPoller) wake() error {
|
||||
buf := make([]byte, 1)
|
||||
n, errno := unix.Write(poller.pipe[1], buf)
|
||||
if n == -1 {
|
||||
if errno == unix.EAGAIN {
|
||||
// Buffer is full, poller will wake.
|
||||
return nil
|
||||
}
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (poller *fdPoller) clearWake() error {
|
||||
// You have to be woken up a LOT in order to get to 100!
|
||||
buf := make([]byte, 100)
|
||||
n, errno := unix.Read(poller.pipe[0], buf)
|
||||
if n == -1 {
|
||||
if errno == unix.EAGAIN {
|
||||
// Buffer is empty, someone else cleared our wake.
|
||||
return nil
|
||||
}
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close all poller file descriptors, but not the one passed to it.
|
||||
func (poller *fdPoller) close() {
|
||||
if poller.pipe[1] != -1 {
|
||||
unix.Close(poller.pipe[1])
|
||||
}
|
||||
if poller.pipe[0] != -1 {
|
||||
unix.Close(poller.pipe[0])
|
||||
}
|
||||
if poller.epfd != -1 {
|
||||
unix.Close(poller.epfd)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,521 @@
|
|||
// 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.
|
||||
|
||||
// +build freebsd openbsd netbsd dragonfly darwin
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct {
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||
|
||||
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||
|
||||
mu sync.Mutex // Protects access to watcher data
|
||||
watches map[string]int // Map of watched file descriptors (key: path).
|
||||
externalWatches map[string]bool // Map of watches added by user of the library.
|
||||
dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
|
||||
paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events.
|
||||
fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
|
||||
isClosed bool // Set to true when Close() is first called
|
||||
}
|
||||
|
||||
type pathInfo struct {
|
||||
name string
|
||||
isDir bool
|
||||
}
|
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
kq, err := kqueue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w := &Watcher{
|
||||
kq: kq,
|
||||
watches: make(map[string]int),
|
||||
dirFlags: make(map[string]uint32),
|
||||
paths: make(map[int]pathInfo),
|
||||
fileExists: make(map[string]bool),
|
||||
externalWatches: make(map[string]bool),
|
||||
Events: make(chan Event),
|
||||
Errors: make(chan error),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
w.mu.Lock()
|
||||
if w.isClosed {
|
||||
w.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
w.isClosed = true
|
||||
|
||||
// copy paths to remove while locked
|
||||
var pathsToRemove = make([]string, 0, len(w.watches))
|
||||
for name := range w.watches {
|
||||
pathsToRemove = append(pathsToRemove, name)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
// unlock before calling Remove, which also locks
|
||||
|
||||
for _, name := range pathsToRemove {
|
||||
w.Remove(name)
|
||||
}
|
||||
|
||||
// send a "quit" message to the reader goroutine
|
||||
close(w.done)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error {
|
||||
w.mu.Lock()
|
||||
w.externalWatches[name] = true
|
||||
w.mu.Unlock()
|
||||
_, err := w.addWatch(name, noteAllEvents)
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove stops watching the the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
name = filepath.Clean(name)
|
||||
w.mu.Lock()
|
||||
watchfd, ok := w.watches[name]
|
||||
w.mu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
|
||||
}
|
||||
|
||||
const registerRemove = unix.EV_DELETE
|
||||
if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
unix.Close(watchfd)
|
||||
|
||||
w.mu.Lock()
|
||||
isDir := w.paths[watchfd].isDir
|
||||
delete(w.watches, name)
|
||||
delete(w.paths, watchfd)
|
||||
delete(w.dirFlags, name)
|
||||
w.mu.Unlock()
|
||||
|
||||
// Find all watched paths that are in this directory that are not external.
|
||||
if isDir {
|
||||
var pathsToRemove []string
|
||||
w.mu.Lock()
|
||||
for _, path := range w.paths {
|
||||
wdir, _ := filepath.Split(path.name)
|
||||
if filepath.Clean(wdir) == name {
|
||||
if !w.externalWatches[path.name] {
|
||||
pathsToRemove = append(pathsToRemove, path.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
w.mu.Unlock()
|
||||
for _, name := range pathsToRemove {
|
||||
// Since these are internal, not much sense in propagating error
|
||||
// to the user, as that will just confuse them with an error about
|
||||
// a path they did not explicitly watch themselves.
|
||||
w.Remove(name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
||||
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
|
||||
|
||||
// keventWaitTime to block on each read from kevent
|
||||
var keventWaitTime = durationToTimespec(100 * time.Millisecond)
|
||||
|
||||
// addWatch adds name to the watched file set.
|
||||
// The flags are interpreted as described in kevent(2).
|
||||
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
|
||||
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
|
||||
var isDir bool
|
||||
// Make ./name and name equivalent
|
||||
name = filepath.Clean(name)
|
||||
|
||||
w.mu.Lock()
|
||||
if w.isClosed {
|
||||
w.mu.Unlock()
|
||||
return "", errors.New("kevent instance already closed")
|
||||
}
|
||||
watchfd, alreadyWatching := w.watches[name]
|
||||
// We already have a watch, but we can still override flags.
|
||||
if alreadyWatching {
|
||||
isDir = w.paths[watchfd].isDir
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
if !alreadyWatching {
|
||||
fi, err := os.Lstat(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Don't watch sockets.
|
||||
if fi.Mode()&os.ModeSocket == os.ModeSocket {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Don't watch named pipes.
|
||||
if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Follow Symlinks
|
||||
// Unfortunately, Linux can add bogus symlinks to watch list without
|
||||
// issue, and Windows can't do symlinks period (AFAIK). To maintain
|
||||
// consistency, we will act like everything is fine. There will simply
|
||||
// be no file events for broken symlinks.
|
||||
// Hence the returns of nil on errors.
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
name, err = filepath.EvalSymlinks(name)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
_, alreadyWatching = w.watches[name]
|
||||
w.mu.Unlock()
|
||||
|
||||
if alreadyWatching {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
fi, err = os.Lstat(name)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
watchfd, err = unix.Open(name, openMode, 0700)
|
||||
if watchfd == -1 {
|
||||
return "", err
|
||||
}
|
||||
|
||||
isDir = fi.IsDir()
|
||||
}
|
||||
|
||||
const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE
|
||||
if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
|
||||
unix.Close(watchfd)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !alreadyWatching {
|
||||
w.mu.Lock()
|
||||
w.watches[name] = watchfd
|
||||
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
if isDir {
|
||||
// Watch the directory if it has not been watched before,
|
||||
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
||||
w.mu.Lock()
|
||||
|
||||
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||||
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||||
// Store flags so this watch can be updated later
|
||||
w.dirFlags[name] = flags
|
||||
w.mu.Unlock()
|
||||
|
||||
if watchDir {
|
||||
if err := w.watchDirectoryFiles(name); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// readEvents reads from kqueue and converts the received kevents into
|
||||
// Event values that it sends down the Events channel.
|
||||
func (w *Watcher) readEvents() {
|
||||
eventBuffer := make([]unix.Kevent_t, 10)
|
||||
|
||||
loop:
|
||||
for {
|
||||
// See if there is a message on the "done" channel
|
||||
select {
|
||||
case <-w.done:
|
||||
break loop
|
||||
default:
|
||||
}
|
||||
|
||||
// Get new events
|
||||
kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
|
||||
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||
if err != nil && err != unix.EINTR {
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
case <-w.done:
|
||||
break loop
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Flush the events we received to the Events channel
|
||||
for len(kevents) > 0 {
|
||||
kevent := &kevents[0]
|
||||
watchfd := int(kevent.Ident)
|
||||
mask := uint32(kevent.Fflags)
|
||||
w.mu.Lock()
|
||||
path := w.paths[watchfd]
|
||||
w.mu.Unlock()
|
||||
event := newEvent(path.name, mask)
|
||||
|
||||
if path.isDir && !(event.Op&Remove == Remove) {
|
||||
// Double check to make sure the directory exists. This can happen when
|
||||
// we do a rm -fr on a recursively watched folders and we receive a
|
||||
// modification event first but the folder has been deleted and later
|
||||
// receive the delete event
|
||||
if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
|
||||
// mark is as delete event
|
||||
event.Op |= Remove
|
||||
}
|
||||
}
|
||||
|
||||
if event.Op&Rename == Rename || event.Op&Remove == Remove {
|
||||
w.Remove(event.Name)
|
||||
w.mu.Lock()
|
||||
delete(w.fileExists, event.Name)
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
||||
w.sendDirectoryChangeEvents(event.Name)
|
||||
} else {
|
||||
// Send the event on the Events channel.
|
||||
select {
|
||||
case w.Events <- event:
|
||||
case <-w.done:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
if event.Op&Remove == Remove {
|
||||
// Look for a file that may have overwritten this.
|
||||
// For example, mv f1 f2 will delete f2, then create f2.
|
||||
if path.isDir {
|
||||
fileDir := filepath.Clean(event.Name)
|
||||
w.mu.Lock()
|
||||
_, found := w.watches[fileDir]
|
||||
w.mu.Unlock()
|
||||
if found {
|
||||
// make sure the directory exists before we watch for changes. When we
|
||||
// do a recursive watch and perform rm -fr, the parent directory might
|
||||
// have gone missing, ignore the missing directory and let the
|
||||
// upcoming delete event remove the watch from the parent directory.
|
||||
if _, err := os.Lstat(fileDir); err == nil {
|
||||
w.sendDirectoryChangeEvents(fileDir)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filePath := filepath.Clean(event.Name)
|
||||
if fileInfo, err := os.Lstat(filePath); err == nil {
|
||||
w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move to next event
|
||||
kevents = kevents[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup
|
||||
err := unix.Close(w.kq)
|
||||
if err != nil {
|
||||
// only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors.
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
close(w.Events)
|
||||
close(w.Errors)
|
||||
}
|
||||
|
||||
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
||||
func newEvent(name string, mask uint32) Event {
|
||||
e := Event{Name: name}
|
||||
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
||||
e.Op |= Remove
|
||||
}
|
||||
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
||||
e.Op |= Write
|
||||
}
|
||||
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
||||
e.Op |= Rename
|
||||
}
|
||||
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
|
||||
e.Op |= Chmod
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func newCreateEvent(name string) Event {
|
||||
return Event{Name: name, Op: Create}
|
||||
}
|
||||
|
||||
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
||||
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
||||
// Get all files
|
||||
files, err := ioutil.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, fileInfo := range files {
|
||||
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
w.fileExists[filePath] = true
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendDirectoryEvents searches the directory for newly created files
|
||||
// and sends them over the event channel. This functionality is to have
|
||||
// the BSD version of fsnotify match Linux inotify which provides a
|
||||
// create event for files created in a watched directory.
|
||||
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
||||
// Get all files
|
||||
files, err := ioutil.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Search for new files
|
||||
for _, fileInfo := range files {
|
||||
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||
err := w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
|
||||
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
|
||||
w.mu.Lock()
|
||||
_, doesExist := w.fileExists[filePath]
|
||||
w.mu.Unlock()
|
||||
if !doesExist {
|
||||
// Send create event
|
||||
select {
|
||||
case w.Events <- newCreateEvent(filePath):
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// like watchDirectoryFiles (but without doing another ReadDir)
|
||||
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
w.fileExists[filePath] = true
|
||||
w.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
|
||||
if fileInfo.IsDir() {
|
||||
// mimic Linux providing delete events for subdirectories
|
||||
// but preserve the flags used if currently watching subdirectory
|
||||
w.mu.Lock()
|
||||
flags := w.dirFlags[name]
|
||||
w.mu.Unlock()
|
||||
|
||||
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
|
||||
return w.addWatch(name, flags)
|
||||
}
|
||||
|
||||
// watch file to mimic Linux inotify
|
||||
return w.addWatch(name, noteAllEvents)
|
||||
}
|
||||
|
||||
// kqueue creates a new kernel event queue and returns a descriptor.
|
||||
func kqueue() (kq int, err error) {
|
||||
kq, err = unix.Kqueue()
|
||||
if kq == -1 {
|
||||
return kq, err
|
||||
}
|
||||
return kq, nil
|
||||
}
|
||||
|
||||
// register events with the queue
|
||||
func register(kq int, fds []int, flags int, fflags uint32) error {
|
||||
changes := make([]unix.Kevent_t, len(fds))
|
||||
|
||||
for i, fd := range fds {
|
||||
// SetKevent converts int to the platform-specific types:
|
||||
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
||||
changes[i].Fflags = fflags
|
||||
}
|
||||
|
||||
// register the events
|
||||
success, err := unix.Kevent(kq, changes, nil, nil)
|
||||
if success == -1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// read retrieves pending events, or waits until an event occurs.
|
||||
// A timeout of nil blocks indefinitely, while 0 polls the queue.
|
||||
func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
|
||||
n, err := unix.Kevent(kq, nil, events, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return events[0:n], nil
|
||||
}
|
||||
|
||||
// durationToTimespec prepares a timeout value
|
||||
func durationToTimespec(d time.Duration) unix.Timespec {
|
||||
return unix.NsecToTimespec(d.Nanoseconds())
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// 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.
|
||||
|
||||
// +build freebsd openbsd netbsd dragonfly
|
||||
|
||||
package fsnotify
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const openMode = unix.O_NONBLOCK | unix.O_RDONLY
|
12
tests/tools/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
generated
vendored
Normal file
12
tests/tools/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
// 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.
|
||||
|
||||
// +build darwin
|
||||
|
||||
package fsnotify
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// note: this constant is not defined on BSD
|
||||
const openMode = unix.O_EVTONLY
|
|
@ -0,0 +1,561 @@
|
|||
// Copyright 2011 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.
|
||||
|
||||
// +build windows
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct {
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
isClosed bool // Set to true when Close() is first called
|
||||
mu sync.Mutex // Map access
|
||||
port syscall.Handle // Handle to completion port
|
||||
watches watchMap // Map of watches (key: i-number)
|
||||
input chan *input // Inputs to the reader are sent on this channel
|
||||
quit chan chan<- error
|
||||
}
|
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
|
||||
if e != nil {
|
||||
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
|
||||
}
|
||||
w := &Watcher{
|
||||
port: port,
|
||||
watches: make(watchMap),
|
||||
input: make(chan *input, 1),
|
||||
Events: make(chan Event, 50),
|
||||
Errors: make(chan error),
|
||||
quit: make(chan chan<- error, 1),
|
||||
}
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
if w.isClosed {
|
||||
return nil
|
||||
}
|
||||
w.isClosed = true
|
||||
|
||||
// Send "quit" message to the reader goroutine
|
||||
ch := make(chan error)
|
||||
w.quit <- ch
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-ch
|
||||
}
|
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error {
|
||||
if w.isClosed {
|
||||
return errors.New("watcher already closed")
|
||||
}
|
||||
in := &input{
|
||||
op: opAddWatch,
|
||||
path: filepath.Clean(name),
|
||||
flags: sysFSALLEVENTS,
|
||||
reply: make(chan error),
|
||||
}
|
||||
w.input <- in
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-in.reply
|
||||
}
|
||||
|
||||
// Remove stops watching the the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
in := &input{
|
||||
op: opRemoveWatch,
|
||||
path: filepath.Clean(name),
|
||||
reply: make(chan error),
|
||||
}
|
||||
w.input <- in
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-in.reply
|
||||
}
|
||||
|
||||
const (
|
||||
// Options for AddWatch
|
||||
sysFSONESHOT = 0x80000000
|
||||
sysFSONLYDIR = 0x1000000
|
||||
|
||||
// Events
|
||||
sysFSACCESS = 0x1
|
||||
sysFSALLEVENTS = 0xfff
|
||||
sysFSATTRIB = 0x4
|
||||
sysFSCLOSE = 0x18
|
||||
sysFSCREATE = 0x100
|
||||
sysFSDELETE = 0x200
|
||||
sysFSDELETESELF = 0x400
|
||||
sysFSMODIFY = 0x2
|
||||
sysFSMOVE = 0xc0
|
||||
sysFSMOVEDFROM = 0x40
|
||||
sysFSMOVEDTO = 0x80
|
||||
sysFSMOVESELF = 0x800
|
||||
|
||||
// Special events
|
||||
sysFSIGNORED = 0x8000
|
||||
sysFSQOVERFLOW = 0x4000
|
||||
)
|
||||
|
||||
func newEvent(name string, mask uint32) Event {
|
||||
e := Event{Name: name}
|
||||
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
||||
e.Op |= Create
|
||||
}
|
||||
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
||||
e.Op |= Remove
|
||||
}
|
||||
if mask&sysFSMODIFY == sysFSMODIFY {
|
||||
e.Op |= Write
|
||||
}
|
||||
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
||||
e.Op |= Rename
|
||||
}
|
||||
if mask&sysFSATTRIB == sysFSATTRIB {
|
||||
e.Op |= Chmod
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
const (
|
||||
opAddWatch = iota
|
||||
opRemoveWatch
|
||||
)
|
||||
|
||||
const (
|
||||
provisional uint64 = 1 << (32 + iota)
|
||||
)
|
||||
|
||||
type input struct {
|
||||
op int
|
||||
path string
|
||||
flags uint32
|
||||
reply chan error
|
||||
}
|
||||
|
||||
type inode struct {
|
||||
handle syscall.Handle
|
||||
volume uint32
|
||||
index uint64
|
||||
}
|
||||
|
||||
type watch struct {
|
||||
ov syscall.Overlapped
|
||||
ino *inode // i-number
|
||||
path string // Directory path
|
||||
mask uint64 // Directory itself is being watched with these notify flags
|
||||
names map[string]uint64 // Map of names being watched and their notify flags
|
||||
rename string // Remembers the old name while renaming a file
|
||||
buf [4096]byte
|
||||
}
|
||||
|
||||
type indexMap map[uint64]*watch
|
||||
type watchMap map[uint32]indexMap
|
||||
|
||||
func (w *Watcher) wakeupReader() error {
|
||||
e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
||||
if e != nil {
|
||||
return os.NewSyscallError("PostQueuedCompletionStatus", e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDir(pathname string) (dir string, err error) {
|
||||
attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
|
||||
if e != nil {
|
||||
return "", os.NewSyscallError("GetFileAttributes", e)
|
||||
}
|
||||
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||
dir = pathname
|
||||
} else {
|
||||
dir, _ = filepath.Split(pathname)
|
||||
dir = filepath.Clean(dir)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getIno(path string) (ino *inode, err error) {
|
||||
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
|
||||
syscall.FILE_LIST_DIRECTORY,
|
||||
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
|
||||
nil, syscall.OPEN_EXISTING,
|
||||
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
|
||||
if e != nil {
|
||||
return nil, os.NewSyscallError("CreateFile", e)
|
||||
}
|
||||
var fi syscall.ByHandleFileInformation
|
||||
if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
|
||||
syscall.CloseHandle(h)
|
||||
return nil, os.NewSyscallError("GetFileInformationByHandle", e)
|
||||
}
|
||||
ino = &inode{
|
||||
handle: h,
|
||||
volume: fi.VolumeSerialNumber,
|
||||
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
||||
}
|
||||
return ino, nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (m watchMap) get(ino *inode) *watch {
|
||||
if i := m[ino.volume]; i != nil {
|
||||
return i[ino.index]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (m watchMap) set(ino *inode, watch *watch) {
|
||||
i := m[ino.volume]
|
||||
if i == nil {
|
||||
i = make(indexMap)
|
||||
m[ino.volume] = i
|
||||
}
|
||||
i[ino.index] = watch
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) addWatch(pathname string, flags uint64) error {
|
||||
dir, err := getDir(pathname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if flags&sysFSONLYDIR != 0 && pathname != dir {
|
||||
return nil
|
||||
}
|
||||
ino, err := getIno(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.mu.Lock()
|
||||
watchEntry := w.watches.get(ino)
|
||||
w.mu.Unlock()
|
||||
if watchEntry == nil {
|
||||
if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
|
||||
syscall.CloseHandle(ino.handle)
|
||||
return os.NewSyscallError("CreateIoCompletionPort", e)
|
||||
}
|
||||
watchEntry = &watch{
|
||||
ino: ino,
|
||||
path: dir,
|
||||
names: make(map[string]uint64),
|
||||
}
|
||||
w.mu.Lock()
|
||||
w.watches.set(ino, watchEntry)
|
||||
w.mu.Unlock()
|
||||
flags |= provisional
|
||||
} else {
|
||||
syscall.CloseHandle(ino.handle)
|
||||
}
|
||||
if pathname == dir {
|
||||
watchEntry.mask |= flags
|
||||
} else {
|
||||
watchEntry.names[filepath.Base(pathname)] |= flags
|
||||
}
|
||||
if err = w.startRead(watchEntry); err != nil {
|
||||
return err
|
||||
}
|
||||
if pathname == dir {
|
||||
watchEntry.mask &= ^provisional
|
||||
} else {
|
||||
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) remWatch(pathname string) error {
|
||||
dir, err := getDir(pathname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ino, err := getIno(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.mu.Lock()
|
||||
watch := w.watches.get(ino)
|
||||
w.mu.Unlock()
|
||||
if watch == nil {
|
||||
return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
|
||||
}
|
||||
if pathname == dir {
|
||||
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||
watch.mask = 0
|
||||
} else {
|
||||
name := filepath.Base(pathname)
|
||||
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
|
||||
delete(watch.names, name)
|
||||
}
|
||||
return w.startRead(watch)
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) deleteWatch(watch *watch) {
|
||||
for name, mask := range watch.names {
|
||||
if mask&provisional == 0 {
|
||||
w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
|
||||
}
|
||||
delete(watch.names, name)
|
||||
}
|
||||
if watch.mask != 0 {
|
||||
if watch.mask&provisional == 0 {
|
||||
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||
}
|
||||
watch.mask = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) startRead(watch *watch) error {
|
||||
if e := syscall.CancelIo(watch.ino.handle); e != nil {
|
||||
w.Errors <- os.NewSyscallError("CancelIo", e)
|
||||
w.deleteWatch(watch)
|
||||
}
|
||||
mask := toWindowsFlags(watch.mask)
|
||||
for _, m := range watch.names {
|
||||
mask |= toWindowsFlags(m)
|
||||
}
|
||||
if mask == 0 {
|
||||
if e := syscall.CloseHandle(watch.ino.handle); e != nil {
|
||||
w.Errors <- os.NewSyscallError("CloseHandle", e)
|
||||
}
|
||||
w.mu.Lock()
|
||||
delete(w.watches[watch.ino.volume], watch.ino.index)
|
||||
w.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
|
||||
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
|
||||
if e != nil {
|
||||
err := os.NewSyscallError("ReadDirectoryChanges", e)
|
||||
if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
||||
// Watched directory was probably removed
|
||||
if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) {
|
||||
if watch.mask&sysFSONESHOT != 0 {
|
||||
watch.mask = 0
|
||||
}
|
||||
}
|
||||
err = nil
|
||||
}
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readEvents reads from the I/O completion port, converts the
|
||||
// received events into Event objects and sends them via the Events channel.
|
||||
// Entry point to the I/O thread.
|
||||
func (w *Watcher) readEvents() {
|
||||
var (
|
||||
n, key uint32
|
||||
ov *syscall.Overlapped
|
||||
)
|
||||
runtime.LockOSThread()
|
||||
|
||||
for {
|
||||
e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
|
||||
watch := (*watch)(unsafe.Pointer(ov))
|
||||
|
||||
if watch == nil {
|
||||
select {
|
||||
case ch := <-w.quit:
|
||||
w.mu.Lock()
|
||||
var indexes []indexMap
|
||||
for _, index := range w.watches {
|
||||
indexes = append(indexes, index)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
for _, index := range indexes {
|
||||
for _, watch := range index {
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
if e := syscall.CloseHandle(w.port); e != nil {
|
||||
err = os.NewSyscallError("CloseHandle", e)
|
||||
}
|
||||
close(w.Events)
|
||||
close(w.Errors)
|
||||
ch <- err
|
||||
return
|
||||
case in := <-w.input:
|
||||
switch in.op {
|
||||
case opAddWatch:
|
||||
in.reply <- w.addWatch(in.path, uint64(in.flags))
|
||||
case opRemoveWatch:
|
||||
in.reply <- w.remWatch(in.path)
|
||||
}
|
||||
default:
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch e {
|
||||
case syscall.ERROR_MORE_DATA:
|
||||
if watch == nil {
|
||||
w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
|
||||
} else {
|
||||
// The i/o succeeded but the buffer is full.
|
||||
// In theory we should be building up a full packet.
|
||||
// In practice we can get away with just carrying on.
|
||||
n = uint32(unsafe.Sizeof(watch.buf))
|
||||
}
|
||||
case syscall.ERROR_ACCESS_DENIED:
|
||||
// Watched directory was probably removed
|
||||
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
continue
|
||||
case syscall.ERROR_OPERATION_ABORTED:
|
||||
// CancelIo was called on this handle
|
||||
continue
|
||||
default:
|
||||
w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
|
||||
continue
|
||||
case nil:
|
||||
}
|
||||
|
||||
var offset uint32
|
||||
for {
|
||||
if n == 0 {
|
||||
w.Events <- newEvent("", sysFSQOVERFLOW)
|
||||
w.Errors <- errors.New("short read in readEvents()")
|
||||
break
|
||||
}
|
||||
|
||||
// Point "raw" to the event in the buffer
|
||||
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
||||
buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
|
||||
name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
|
||||
fullname := filepath.Join(watch.path, name)
|
||||
|
||||
var mask uint64
|
||||
switch raw.Action {
|
||||
case syscall.FILE_ACTION_REMOVED:
|
||||
mask = sysFSDELETESELF
|
||||
case syscall.FILE_ACTION_MODIFIED:
|
||||
mask = sysFSMODIFY
|
||||
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||
watch.rename = name
|
||||
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||
if watch.names[watch.rename] != 0 {
|
||||
watch.names[name] |= watch.names[watch.rename]
|
||||
delete(watch.names, watch.rename)
|
||||
mask = sysFSMOVESELF
|
||||
}
|
||||
}
|
||||
|
||||
sendNameEvent := func() {
|
||||
if w.sendEvent(fullname, watch.names[name]&mask) {
|
||||
if watch.names[name]&sysFSONESHOT != 0 {
|
||||
delete(watch.names, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||
sendNameEvent()
|
||||
}
|
||||
if raw.Action == syscall.FILE_ACTION_REMOVED {
|
||||
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
|
||||
delete(watch.names, name)
|
||||
}
|
||||
if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
|
||||
if watch.mask&sysFSONESHOT != 0 {
|
||||
watch.mask = 0
|
||||
}
|
||||
}
|
||||
if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||
fullname = filepath.Join(watch.path, watch.rename)
|
||||
sendNameEvent()
|
||||
}
|
||||
|
||||
// Move to the next event in the buffer
|
||||
if raw.NextEntryOffset == 0 {
|
||||
break
|
||||
}
|
||||
offset += raw.NextEntryOffset
|
||||
|
||||
// Error!
|
||||
if offset >= n {
|
||||
w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.startRead(watch); err != nil {
|
||||
w.Errors <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
||||
if mask == 0 {
|
||||
return false
|
||||
}
|
||||
event := newEvent(name, uint32(mask))
|
||||
select {
|
||||
case ch := <-w.quit:
|
||||
w.quit <- ch
|
||||
case w.Events <- event:
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func toWindowsFlags(mask uint64) uint32 {
|
||||
var m uint32
|
||||
if mask&sysFSACCESS != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
|
||||
}
|
||||
if mask&sysFSMODIFY != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||
}
|
||||
if mask&sysFSATTRIB != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
|
||||
}
|
||||
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func toFSnotifyFlags(action uint32) uint64 {
|
||||
switch action {
|
||||
case syscall.FILE_ACTION_ADDED:
|
||||
return sysFSCREATE
|
||||
case syscall.FILE_ACTION_REMOVED:
|
||||
return sysFSDELETE
|
||||
case syscall.FILE_ACTION_MODIFIED:
|
||||
return sysFSMODIFY
|
||||
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||
return sysFSMOVEDFROM
|
||||
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||
return sysFSMOVEDTO
|
||||
}
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018-2019 Alekseev Artem
|
||||
Copyright (c) 2018-2019 Ravil Bikbulatov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
102
tests/tools/vendor/github.com/go-critic/go-critic/checkers/appendAssign_checker.go
generated
vendored
Normal file
102
tests/tools/vendor/github.com/go-critic/go-critic/checkers/appendAssign_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astequal"
|
||||
"github.com/go-toolsmith/astp"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "appendAssign"
|
||||
info.Tags = []string{"diagnostic"}
|
||||
info.Summary = "Detects suspicious append result assignments"
|
||||
info.Before = `
|
||||
p.positives = append(p.negatives, x)
|
||||
p.negatives = append(p.negatives, y)`
|
||||
info.After = `
|
||||
p.positives = append(p.positives, x)
|
||||
p.negatives = append(p.negatives, y)`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForStmt(&appendAssignChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type appendAssignChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *appendAssignChecker) VisitStmt(stmt ast.Stmt) {
|
||||
assign, ok := stmt.(*ast.AssignStmt)
|
||||
if !ok || assign.Tok != token.ASSIGN || len(assign.Lhs) != len(assign.Rhs) {
|
||||
return
|
||||
}
|
||||
for i, rhs := range assign.Rhs {
|
||||
call, ok := rhs.(*ast.CallExpr)
|
||||
if !ok || qualifiedName(call.Fun) != "append" {
|
||||
continue
|
||||
}
|
||||
c.checkAppend(assign.Lhs[i], call)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *appendAssignChecker) checkAppend(x ast.Expr, call *ast.CallExpr) {
|
||||
if call.Ellipsis != token.NoPos {
|
||||
// Try to detect `xs = append(ys, xs...)` idiom.
|
||||
for _, arg := range call.Args[1:] {
|
||||
y := arg
|
||||
if arg, ok := arg.(*ast.SliceExpr); ok {
|
||||
y = arg.X
|
||||
}
|
||||
if astequal.Expr(x, y) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch x := x.(type) {
|
||||
case *ast.Ident:
|
||||
if x.Name == "_" {
|
||||
return // Don't check assignments to blank ident
|
||||
}
|
||||
case *ast.IndexExpr:
|
||||
if !astp.IsIndexExpr(call.Args[0]) {
|
||||
// Most likely `m[k] = append(x, ...)`
|
||||
// pattern, where x was retrieved by m[k] before.
|
||||
//
|
||||
// TODO: it's possible to record such map/slice reads
|
||||
// and check whether it was done before this call.
|
||||
// But for now, treat it like x belongs to m[k].
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch y := call.Args[0].(type) {
|
||||
case *ast.SliceExpr:
|
||||
if _, ok := c.ctx.TypesInfo.TypeOf(y.X).(*types.Array); ok {
|
||||
// Arrays are frequently used as scratch storages.
|
||||
return
|
||||
}
|
||||
c.matchSlices(call, x, y.X)
|
||||
case *ast.IndexExpr, *ast.Ident, *ast.SelectorExpr:
|
||||
c.matchSlices(call, x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *appendAssignChecker) matchSlices(cause ast.Node, x, y ast.Expr) {
|
||||
if !astequal.Expr(x, astutil.Unparen(y)) {
|
||||
c.warn(cause)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *appendAssignChecker) warn(cause ast.Node) {
|
||||
c.ctx.Warn(cause, "append result not assigned to the same slice")
|
||||
}
|
102
tests/tools/vendor/github.com/go-critic/go-critic/checkers/appendCombine_checker.go
generated
vendored
Normal file
102
tests/tools/vendor/github.com/go-critic/go-critic/checkers/appendCombine_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
"github.com/go-toolsmith/astequal"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "appendCombine"
|
||||
info.Tags = []string{"performance"}
|
||||
info.Summary = "Detects `append` chains to the same slice that can be done in a single `append` call"
|
||||
info.Before = `
|
||||
xs = append(xs, 1)
|
||||
xs = append(xs, 2)`
|
||||
info.After = `xs = append(xs, 1, 2)`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForStmtList(&appendCombineChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type appendCombineChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *appendCombineChecker) VisitStmtList(list []ast.Stmt) {
|
||||
var cause ast.Node // First append
|
||||
var slice ast.Expr // Slice being appended to
|
||||
chain := 0 // How much appends in a row we've seen
|
||||
|
||||
// Break the chain.
|
||||
// If enough appends are in chain, print warning.
|
||||
flush := func() {
|
||||
if chain > 1 {
|
||||
c.warn(cause, chain)
|
||||
}
|
||||
chain = 0
|
||||
slice = nil
|
||||
}
|
||||
|
||||
for _, stmt := range list {
|
||||
call := c.matchAppend(stmt, slice)
|
||||
if call == nil {
|
||||
flush()
|
||||
continue
|
||||
}
|
||||
|
||||
if chain == 0 {
|
||||
// First append in a chain.
|
||||
chain = 1
|
||||
slice = call.Args[0]
|
||||
cause = stmt
|
||||
} else {
|
||||
chain++
|
||||
}
|
||||
}
|
||||
|
||||
// Required for printing chains that consist of trailing
|
||||
// statements from the list.
|
||||
flush()
|
||||
}
|
||||
|
||||
func (c *appendCombineChecker) matchAppend(stmt ast.Stmt, slice ast.Expr) *ast.CallExpr {
|
||||
// Seeking for:
|
||||
// slice = append(slice, xs...)
|
||||
// xs are 0-N append arguments, but not variadic argument,
|
||||
// because it makes append combining impossible.
|
||||
|
||||
assign := astcast.ToAssignStmt(stmt)
|
||||
if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
call, ok := assign.Rhs[0].(*ast.CallExpr)
|
||||
{
|
||||
cond := ok &&
|
||||
qualifiedName(call.Fun) == "append" &&
|
||||
call.Ellipsis == token.NoPos &&
|
||||
astequal.Expr(assign.Lhs[0], call.Args[0])
|
||||
if !cond {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check that current append slice match previous append slice.
|
||||
// Otherwise we should break the chain.
|
||||
if slice == nil || astequal.Expr(slice, call.Args[0]) {
|
||||
return call
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *appendCombineChecker) warn(cause ast.Node, chain int) {
|
||||
c.ctx.Warn(cause, "can combine chain of %d appends into one", chain)
|
||||
}
|
98
tests/tools/vendor/github.com/go-critic/go-critic/checkers/argOrder_checker.go
generated
vendored
Normal file
98
tests/tools/vendor/github.com/go-critic/go-critic/checkers/argOrder_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
"github.com/go-toolsmith/astcopy"
|
||||
"github.com/go-toolsmith/astp"
|
||||
"github.com/go-toolsmith/typep"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "argOrder"
|
||||
info.Tags = []string{"diagnostic", "experimental"}
|
||||
info.Summary = "Detects suspicious arguments order"
|
||||
info.Before = `strings.HasPrefix("#", userpass)`
|
||||
info.After = `strings.HasPrefix(userpass, "#")`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForExpr(&argOrderChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type argOrderChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *argOrderChecker) VisitExpr(expr ast.Expr) {
|
||||
call := astcast.ToCallExpr(expr)
|
||||
|
||||
// For now only handle functions of 2 args.
|
||||
// TODO(Quasilyte): generalize the algorithm and add more patterns.
|
||||
if len(call.Args) != 2 {
|
||||
return
|
||||
}
|
||||
|
||||
calledExpr := astcast.ToSelectorExpr(call.Fun)
|
||||
obj, ok := c.ctx.TypesInfo.ObjectOf(astcast.ToIdent(calledExpr.X)).(*types.PkgName)
|
||||
if !ok || !isStdlibPkg(obj.Imported()) {
|
||||
return
|
||||
}
|
||||
|
||||
x := call.Args[0]
|
||||
y := call.Args[1]
|
||||
switch calledExpr.Sel.Name {
|
||||
case "HasPrefix", "HasSuffix", "Contains", "TrimPrefix", "TrimSuffix", "Split":
|
||||
if obj.Name() != "bytes" && obj.Name() != "strings" {
|
||||
return
|
||||
}
|
||||
if c.isConstLiteral(x) && !c.isConstLiteral(y) {
|
||||
c.warn(call)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *argOrderChecker) isConstLiteral(x ast.Expr) bool {
|
||||
if c.ctx.TypesInfo.Types[x].Value != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Also permit byte slices.
|
||||
switch x := x.(type) {
|
||||
case *ast.CallExpr:
|
||||
// Handle `[]byte("abc")` as well.
|
||||
if len(x.Args) != 1 || !astp.IsBasicLit(x.Args[0]) {
|
||||
return false
|
||||
}
|
||||
typ, ok := c.ctx.TypesInfo.TypeOf(x.Fun).(*types.Slice)
|
||||
return ok && typep.HasUint8Kind(typ.Elem())
|
||||
|
||||
case *ast.CompositeLit:
|
||||
// Check if it's a const byte slice.
|
||||
typ, ok := c.ctx.TypesInfo.TypeOf(x).(*types.Slice)
|
||||
if !ok || !typep.HasUint8Kind(typ.Elem()) {
|
||||
return false
|
||||
}
|
||||
for _, elt := range x.Elts {
|
||||
if !astp.IsBasicLit(elt) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *argOrderChecker) warn(call *ast.CallExpr) {
|
||||
fixed := astcopy.CallExpr(call)
|
||||
fixed.Args[0], fixed.Args[1] = fixed.Args[1], fixed.Args[0]
|
||||
c.ctx.Warn(call, "probably meant `%s`", fixed)
|
||||
}
|
102
tests/tools/vendor/github.com/go-critic/go-critic/checkers/assignOp_checker.go
generated
vendored
Normal file
102
tests/tools/vendor/github.com/go-critic/go-critic/checkers/assignOp_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcopy"
|
||||
"github.com/go-toolsmith/astequal"
|
||||
"github.com/go-toolsmith/typep"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "assignOp"
|
||||
info.Tags = []string{"style"}
|
||||
info.Summary = "Detects assignments that can be simplified by using assignment operators"
|
||||
info.Before = `x = x * 2`
|
||||
info.After = `x *= 2`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForStmt(&assignOpChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type assignOpChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *assignOpChecker) VisitStmt(stmt ast.Stmt) {
|
||||
assign, ok := stmt.(*ast.AssignStmt)
|
||||
cond := ok &&
|
||||
assign.Tok == token.ASSIGN &&
|
||||
len(assign.Lhs) == 1 &&
|
||||
len(assign.Rhs) == 1 &&
|
||||
typep.SideEffectFree(c.ctx.TypesInfo, assign.Lhs[0])
|
||||
if !cond {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(quasilyte): can take commutativity into account.
|
||||
expr, ok := assign.Rhs[0].(*ast.BinaryExpr)
|
||||
if !ok || !astequal.Expr(assign.Lhs[0], expr.X) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(quasilyte): perform unparen?
|
||||
switch expr.Op {
|
||||
case token.MUL:
|
||||
c.warn(assign, token.MUL_ASSIGN, expr.Y)
|
||||
case token.QUO:
|
||||
c.warn(assign, token.QUO_ASSIGN, expr.Y)
|
||||
case token.REM:
|
||||
c.warn(assign, token.REM_ASSIGN, expr.Y)
|
||||
case token.ADD:
|
||||
c.warn(assign, token.ADD_ASSIGN, expr.Y)
|
||||
case token.SUB:
|
||||
c.warn(assign, token.SUB_ASSIGN, expr.Y)
|
||||
case token.AND:
|
||||
c.warn(assign, token.AND_ASSIGN, expr.Y)
|
||||
case token.OR:
|
||||
c.warn(assign, token.OR_ASSIGN, expr.Y)
|
||||
case token.XOR:
|
||||
c.warn(assign, token.XOR_ASSIGN, expr.Y)
|
||||
case token.SHL:
|
||||
c.warn(assign, token.SHL_ASSIGN, expr.Y)
|
||||
case token.SHR:
|
||||
c.warn(assign, token.SHR_ASSIGN, expr.Y)
|
||||
case token.AND_NOT:
|
||||
c.warn(assign, token.AND_NOT_ASSIGN, expr.Y)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *assignOpChecker) warn(cause *ast.AssignStmt, op token.Token, rhs ast.Expr) {
|
||||
suggestion := c.simplify(cause, op, rhs)
|
||||
c.ctx.Warn(cause, "replace `%s` with `%s`", cause, suggestion)
|
||||
}
|
||||
|
||||
func (c *assignOpChecker) simplify(cause *ast.AssignStmt, op token.Token, rhs ast.Expr) ast.Stmt {
|
||||
if lit, ok := rhs.(*ast.BasicLit); ok && lit.Kind == token.INT && lit.Value == "1" {
|
||||
switch op {
|
||||
case token.ADD_ASSIGN:
|
||||
return &ast.IncDecStmt{
|
||||
X: cause.Lhs[0],
|
||||
TokPos: cause.TokPos,
|
||||
Tok: token.INC,
|
||||
}
|
||||
case token.SUB_ASSIGN:
|
||||
return &ast.IncDecStmt{
|
||||
X: cause.Lhs[0],
|
||||
TokPos: cause.TokPos,
|
||||
Tok: token.DEC,
|
||||
}
|
||||
}
|
||||
}
|
||||
suggestion := astcopy.AssignStmt(cause)
|
||||
suggestion.Tok = op
|
||||
suggestion.Rhs[0] = rhs
|
||||
return suggestion
|
||||
}
|
63
tests/tools/vendor/github.com/go-critic/go-critic/checkers/badCall_checker.go
generated
vendored
Normal file
63
tests/tools/vendor/github.com/go-critic/go-critic/checkers/badCall_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
"github.com/go-toolsmith/astcopy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "badCall"
|
||||
info.Tags = []string{"diagnostic", "experimental"}
|
||||
info.Summary = "Detects suspicious function calls"
|
||||
info.Before = `strings.Replace(s, from, to, 0)`
|
||||
info.After = `strings.Replace(s, from, to, -1)`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForExpr(&badCallChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type badCallChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *badCallChecker) VisitExpr(expr ast.Expr) {
|
||||
call := astcast.ToCallExpr(expr)
|
||||
if len(call.Args) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(quasilyte): handle methods.
|
||||
|
||||
switch qualifiedName(call.Fun) {
|
||||
case "strings.Replace", "bytes.Replace":
|
||||
if n := astcast.ToBasicLit(call.Args[3]); n.Value == "0" {
|
||||
c.warnBadArg(n, "-1")
|
||||
}
|
||||
case "strings.SplitN", "bytes.SplitN":
|
||||
if n := astcast.ToBasicLit(call.Args[2]); n.Value == "0" {
|
||||
c.warnBadArg(n, "-1")
|
||||
}
|
||||
case "append":
|
||||
if len(call.Args) == 1 {
|
||||
c.warnAppend(call)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *badCallChecker) warnBadArg(badArg *ast.BasicLit, correction string) {
|
||||
goodArg := astcopy.BasicLit(badArg)
|
||||
goodArg.Value = correction
|
||||
c.ctx.Warn(badArg, "suspicious arg %s, probably meant %s",
|
||||
badArg, goodArg)
|
||||
}
|
||||
|
||||
func (c *badCallChecker) warnAppend(call *ast.CallExpr) {
|
||||
c.ctx.Warn(call, "no-op append call, probably missing arguments")
|
||||
}
|
147
tests/tools/vendor/github.com/go-critic/go-critic/checkers/badCond_checker.go
generated
vendored
Normal file
147
tests/tools/vendor/github.com/go-critic/go-critic/checkers/badCond_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,147 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
|
||||
"github.com/go-critic/go-critic/checkers/internal/lintutil"
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
"github.com/go-toolsmith/astcopy"
|
||||
"github.com/go-toolsmith/astequal"
|
||||
"github.com/go-toolsmith/typep"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "badCond"
|
||||
info.Tags = []string{"diagnostic", "experimental"}
|
||||
info.Summary = "Detects suspicious condition expressions"
|
||||
info.Before = `
|
||||
for i := 0; i > n; i++ {
|
||||
xs[i] = 0
|
||||
}`
|
||||
info.After = `
|
||||
for i := 0; i < n; i++ {
|
||||
xs[i] = 0
|
||||
}`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForFuncDecl(&badCondChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type badCondChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *badCondChecker) VisitFuncDecl(decl *ast.FuncDecl) {
|
||||
ast.Inspect(decl.Body, func(n ast.Node) bool {
|
||||
switch n := n.(type) {
|
||||
case *ast.ForStmt:
|
||||
c.checkForStmt(n)
|
||||
case ast.Expr:
|
||||
c.checkExpr(n)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (c *badCondChecker) checkExpr(expr ast.Expr) {
|
||||
// TODO(Quasilyte): recognize more patterns.
|
||||
|
||||
cond := astcast.ToBinaryExpr(expr)
|
||||
lhs := astcast.ToBinaryExpr(astutil.Unparen(cond.X))
|
||||
rhs := astcast.ToBinaryExpr(astutil.Unparen(cond.Y))
|
||||
|
||||
if cond.Op != token.LAND {
|
||||
return
|
||||
}
|
||||
|
||||
// Notes:
|
||||
// `x != a || x != b` handled by go vet.
|
||||
|
||||
// Pattern 1.
|
||||
// `x < a && x > b`; Where `a` is less than `b`.
|
||||
if c.lessAndGreater(lhs, rhs) {
|
||||
c.warnCond(cond, "always false")
|
||||
return
|
||||
}
|
||||
|
||||
// Pattern 2.
|
||||
// `x == a && x == b`
|
||||
//
|
||||
// Valid when `b == a` is intended, but still reported.
|
||||
// We can disable "just suspicious" warnings by default
|
||||
// is users are upset with the current behavior.
|
||||
if c.equalToBoth(lhs, rhs) {
|
||||
c.warnCond(cond, "suspicious")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *badCondChecker) equalToBoth(lhs, rhs *ast.BinaryExpr) bool {
|
||||
return lhs.Op == token.EQL && rhs.Op == token.EQL &&
|
||||
astequal.Expr(lhs.X, rhs.X)
|
||||
}
|
||||
|
||||
func (c *badCondChecker) lessAndGreater(lhs, rhs *ast.BinaryExpr) bool {
|
||||
if lhs.Op != token.LSS || rhs.Op != token.GTR {
|
||||
return false
|
||||
}
|
||||
if !astequal.Expr(lhs.X, rhs.X) {
|
||||
return false
|
||||
}
|
||||
a := c.ctx.TypesInfo.Types[lhs.Y].Value
|
||||
b := c.ctx.TypesInfo.Types[rhs.Y].Value
|
||||
return a != nil && b != nil && constant.Compare(a, token.LSS, b)
|
||||
}
|
||||
|
||||
func (c *badCondChecker) checkForStmt(stmt *ast.ForStmt) {
|
||||
// TODO(Quasilyte): handle other kinds of bad conditionals.
|
||||
|
||||
init := astcast.ToAssignStmt(stmt.Init)
|
||||
if init.Tok != token.DEFINE || len(init.Lhs) != 1 || len(init.Rhs) != 1 {
|
||||
return
|
||||
}
|
||||
if astcast.ToBasicLit(init.Rhs[0]).Value != "0" {
|
||||
return
|
||||
}
|
||||
|
||||
iter := astcast.ToIdent(init.Lhs[0])
|
||||
cond := astcast.ToBinaryExpr(stmt.Cond)
|
||||
if cond.Op != token.GTR || !astequal.Expr(iter, cond.X) {
|
||||
return
|
||||
}
|
||||
if !typep.SideEffectFree(c.ctx.TypesInfo, cond.Y) {
|
||||
return
|
||||
}
|
||||
|
||||
post := astcast.ToIncDecStmt(stmt.Post)
|
||||
if post.Tok != token.INC || !astequal.Expr(iter, post.X) {
|
||||
return
|
||||
}
|
||||
|
||||
mutated := lintutil.CouldBeMutated(c.ctx.TypesInfo, stmt.Body, cond.Y) ||
|
||||
lintutil.CouldBeMutated(c.ctx.TypesInfo, stmt.Body, iter)
|
||||
if mutated {
|
||||
return
|
||||
}
|
||||
|
||||
c.warnForStmt(stmt, cond)
|
||||
}
|
||||
|
||||
func (c *badCondChecker) warnForStmt(cause ast.Node, cond *ast.BinaryExpr) {
|
||||
suggest := astcopy.BinaryExpr(cond)
|
||||
suggest.Op = token.LSS
|
||||
c.ctx.Warn(cause, "`%s` in loop; probably meant `%s`?",
|
||||
cond, suggest)
|
||||
}
|
||||
|
||||
func (c *badCondChecker) warnCond(cond *ast.BinaryExpr, tag string) {
|
||||
c.ctx.Warn(cond, "`%s` condition is %s", cond, tag)
|
||||
}
|
344
tests/tools/vendor/github.com/go-critic/go-critic/checkers/boolExprSimplify_checker.go
generated
vendored
Normal file
344
tests/tools/vendor/github.com/go-critic/go-critic/checkers/boolExprSimplify_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,344 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-critic/go-critic/checkers/internal/lintutil"
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
"github.com/go-toolsmith/astcopy"
|
||||
"github.com/go-toolsmith/astequal"
|
||||
"github.com/go-toolsmith/astp"
|
||||
"github.com/go-toolsmith/typep"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "boolExprSimplify"
|
||||
info.Tags = []string{"style", "experimental"}
|
||||
info.Summary = "Detects bool expressions that can be simplified"
|
||||
info.Before = `
|
||||
a := !(elapsed >= expectElapsedMin)
|
||||
b := !(x) == !(y)`
|
||||
info.After = `
|
||||
a := elapsed < expectElapsedMin
|
||||
b := (x) == (y)`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForExpr(&boolExprSimplifyChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type boolExprSimplifyChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
hasFloats bool
|
||||
}
|
||||
|
||||
func (c *boolExprSimplifyChecker) VisitExpr(x ast.Expr) {
|
||||
if !astp.IsBinaryExpr(x) && !astp.IsUnaryExpr(x) {
|
||||
return
|
||||
}
|
||||
|
||||
// Throw away non-bool expressions and avoid redundant
|
||||
// AST copying below.
|
||||
if typ := c.ctx.TypesInfo.TypeOf(x); typ == nil || !typep.HasBoolKind(typ.Underlying()) {
|
||||
return
|
||||
}
|
||||
|
||||
// We'll loose all types info after a copy,
|
||||
// this is why we record valuable info before doing it.
|
||||
c.hasFloats = lintutil.ContainsNode(x, func(n ast.Node) bool {
|
||||
if x, ok := n.(*ast.BinaryExpr); ok {
|
||||
return typep.HasFloatProp(c.ctx.TypesInfo.TypeOf(x.X).Underlying()) ||
|
||||
typep.HasFloatProp(c.ctx.TypesInfo.TypeOf(x.Y).Underlying())
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
y := c.simplifyBool(astcopy.Expr(x))
|
||||
if !astequal.Expr(x, y) {
|
||||
c.warn(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *boolExprSimplifyChecker) simplifyBool(x ast.Expr) ast.Expr {
|
||||
return astutil.Apply(x, nil, func(cur *astutil.Cursor) bool {
|
||||
return c.doubleNegation(cur) ||
|
||||
c.negatedEquals(cur) ||
|
||||
c.invertComparison(cur) ||
|
||||
c.combineChecks(cur) ||
|
||||
c.removeIncDec(cur) ||
|
||||
c.foldRanges(cur) ||
|
||||
true
|
||||
}).(ast.Expr)
|
||||
}
|
||||
|
||||
func (c *boolExprSimplifyChecker) doubleNegation(cur *astutil.Cursor) bool {
|
||||
neg1 := astcast.ToUnaryExpr(cur.Node())
|
||||
neg2 := astcast.ToUnaryExpr(astutil.Unparen(neg1.X))
|
||||
if neg1.Op == token.NOT && neg2.Op == token.NOT {
|
||||
cur.Replace(astutil.Unparen(neg2.X))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *boolExprSimplifyChecker) negatedEquals(cur *astutil.Cursor) bool {
|
||||
x, ok := cur.Node().(*ast.BinaryExpr)
|
||||
if !ok || x.Op != token.EQL {
|
||||
return false
|
||||
}
|
||||
neg1 := astcast.ToUnaryExpr(x.X)
|
||||
neg2 := astcast.ToUnaryExpr(x.Y)
|
||||
if neg1.Op == token.NOT && neg2.Op == token.NOT {
|
||||
x.X = neg1.X
|
||||
x.Y = neg2.X
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *boolExprSimplifyChecker) invertComparison(cur *astutil.Cursor) bool {
|
||||
if c.hasFloats { // See #673
|
||||
return false
|
||||
}
|
||||
|
||||
neg := astcast.ToUnaryExpr(cur.Node())
|
||||
cmp := astcast.ToBinaryExpr(astutil.Unparen(neg.X))
|
||||
if neg.Op != token.NOT {
|
||||
return false
|
||||
}
|
||||
|
||||
// Replace operator to its negated form.
|
||||
switch cmp.Op {
|
||||
case token.EQL:
|
||||
cmp.Op = token.NEQ
|
||||
case token.NEQ:
|
||||
cmp.Op = token.EQL
|
||||
case token.LSS:
|
||||
cmp.Op = token.GEQ
|
||||
case token.GTR:
|
||||
cmp.Op = token.LEQ
|
||||
case token.LEQ:
|
||||
cmp.Op = token.GTR
|
||||
case token.GEQ:
|
||||
cmp.Op = token.LSS
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
cur.Replace(cmp)
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *boolExprSimplifyChecker) isSafe(x ast.Expr) bool {
|
||||
return typep.SideEffectFree(c.ctx.TypesInfo, x)
|
||||
}
|
||||
|
||||
func (c *boolExprSimplifyChecker) combineChecks(cur *astutil.Cursor) bool {
|
||||
or, ok := cur.Node().(*ast.BinaryExpr)
|
||||
if !ok || or.Op != token.LOR {
|
||||
return false
|
||||
}
|
||||
|
||||
lhs := astcast.ToBinaryExpr(astutil.Unparen(or.X))
|
||||
rhs := astcast.ToBinaryExpr(astutil.Unparen(or.Y))
|
||||
|
||||
if !astequal.Expr(lhs.X, rhs.X) || !astequal.Expr(lhs.Y, rhs.Y) {
|
||||
return false
|
||||
}
|
||||
if !c.isSafe(lhs.X) || !c.isSafe(lhs.Y) {
|
||||
return false
|
||||
}
|
||||
|
||||
combTable := [...]struct {
|
||||
x token.Token
|
||||
y token.Token
|
||||
result token.Token
|
||||
}{
|
||||
{token.GTR, token.EQL, token.GEQ},
|
||||
{token.EQL, token.GTR, token.GEQ},
|
||||
{token.LSS, token.EQL, token.LEQ},
|
||||
{token.EQL, token.LSS, token.LEQ},
|
||||
}
|
||||
for _, comb := range &combTable {
|
||||
if comb.x == lhs.Op && comb.y == rhs.Op {
|
||||
lhs.Op = comb.result
|
||||
cur.Replace(lhs)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *boolExprSimplifyChecker) removeIncDec(cur *astutil.Cursor) bool {
|
||||
cmp := astcast.ToBinaryExpr(cur.Node())
|
||||
|
||||
matchOneWay := func(op token.Token, x, y *ast.BinaryExpr) bool {
|
||||
if x.Op != op || astcast.ToBasicLit(x.Y).Value != "1" {
|
||||
return false
|
||||
}
|
||||
if y.Op == op && astcast.ToBasicLit(y.Y).Value == "1" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
replace := func(lhsOp, rhsOp, replacement token.Token) bool {
|
||||
lhs := astcast.ToBinaryExpr(cmp.X)
|
||||
rhs := astcast.ToBinaryExpr(cmp.Y)
|
||||
switch {
|
||||
case matchOneWay(lhsOp, lhs, rhs):
|
||||
cmp.X = lhs.X
|
||||
cmp.Op = replacement
|
||||
cur.Replace(cmp)
|
||||
return true
|
||||
case matchOneWay(rhsOp, rhs, lhs):
|
||||
cmp.Y = rhs.X
|
||||
cmp.Op = replacement
|
||||
cur.Replace(cmp)
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
switch cmp.Op {
|
||||
case token.GTR:
|
||||
// `x > y-1` => `x >= y`
|
||||
// `x+1 > y` => `x >= y`
|
||||
return replace(token.ADD, token.SUB, token.GEQ)
|
||||
|
||||
case token.GEQ:
|
||||
// `x >= y+1` => `x > y`
|
||||
// `x-1 >= y` => `x > y`
|
||||
return replace(token.SUB, token.ADD, token.GTR)
|
||||
|
||||
case token.LSS:
|
||||
// `x < y+1` => `x <= y`
|
||||
// `x-1 < y` => `x <= y`
|
||||
return replace(token.SUB, token.ADD, token.LEQ)
|
||||
|
||||
case token.LEQ:
|
||||
// `x <= y-1` => `x < y`
|
||||
// `x+1 <= y` => `x < y`
|
||||
return replace(token.ADD, token.SUB, token.LSS)
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *boolExprSimplifyChecker) foldRanges(cur *astutil.Cursor) bool {
|
||||
if c.hasFloats { // See #848
|
||||
return false
|
||||
}
|
||||
|
||||
e, ok := cur.Node().(*ast.BinaryExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
lhs := astcast.ToBinaryExpr(e.X)
|
||||
rhs := astcast.ToBinaryExpr(e.Y)
|
||||
if !c.isSafe(lhs.X) || !c.isSafe(rhs.X) {
|
||||
return false
|
||||
}
|
||||
if !astequal.Expr(lhs.X, rhs.X) {
|
||||
return false
|
||||
}
|
||||
|
||||
c1, ok := c.int64val(lhs.Y)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
c2, ok := c.int64val(rhs.Y)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
type combination struct {
|
||||
lhsOp token.Token
|
||||
rhsOp token.Token
|
||||
rhsDiff int64
|
||||
resDelta int64
|
||||
}
|
||||
match := func(comb *combination) bool {
|
||||
if lhs.Op != comb.lhsOp || rhs.Op != comb.rhsOp {
|
||||
return false
|
||||
}
|
||||
if c2-c1 != comb.rhsDiff {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
switch e.Op {
|
||||
case token.LAND:
|
||||
combTable := [...]combination{
|
||||
// `x > c && x < c+2` => `x == c+1`
|
||||
{token.GTR, token.LSS, 2, 1},
|
||||
// `x >= c && x < c+1` => `x == c`
|
||||
{token.GEQ, token.LSS, 1, 0},
|
||||
// `x > c && x <= c+1` => `x == c+1`
|
||||
{token.GTR, token.LEQ, 1, 1},
|
||||
// `x >= c && x <= c` => `x == c`
|
||||
{token.GEQ, token.LEQ, 0, 0},
|
||||
}
|
||||
for _, comb := range combTable {
|
||||
if match(&comb) {
|
||||
lhs.Op = token.EQL
|
||||
v := c1 + comb.resDelta
|
||||
lhs.Y.(*ast.BasicLit).Value = fmt.Sprint(v)
|
||||
cur.Replace(lhs)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
case token.LOR:
|
||||
combTable := [...]combination{
|
||||
// `x < c || x > c` => `x != c`
|
||||
{token.LSS, token.GTR, 0, 0},
|
||||
// `x <= c || x > c+1` => `x != c+1`
|
||||
{token.LEQ, token.GTR, 1, 1},
|
||||
// `x < c || x >= c+1` => `x != c`
|
||||
{token.LSS, token.GEQ, 1, 0},
|
||||
// `x <= c || x >= c+2` => `x != c+1`
|
||||
{token.LEQ, token.GEQ, 2, 1},
|
||||
}
|
||||
for _, comb := range combTable {
|
||||
if match(&comb) {
|
||||
lhs.Op = token.NEQ
|
||||
v := c1 + comb.resDelta
|
||||
lhs.Y.(*ast.BasicLit).Value = fmt.Sprint(v)
|
||||
cur.Replace(lhs)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *boolExprSimplifyChecker) int64val(x ast.Expr) (int64, bool) {
|
||||
// TODO(Quasilyte): if we had types info, we could use TypesInfo.Types[x].Value,
|
||||
// but since copying erases leaves us without it, only basic literals are handled.
|
||||
lit, ok := x.(*ast.BasicLit)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
v, err := strconv.ParseInt(lit.Value, 10, 64)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return v, true
|
||||
}
|
||||
|
||||
func (c *boolExprSimplifyChecker) warn(cause, suggestion ast.Expr) {
|
||||
c.SkipChilds = true
|
||||
c.ctx.Warn(cause, "can simplify `%s` to `%s`", cause, suggestion)
|
||||
}
|
36
tests/tools/vendor/github.com/go-critic/go-critic/checkers/builtinShadow_checker.go
generated
vendored
Normal file
36
tests/tools/vendor/github.com/go-critic/go-critic/checkers/builtinShadow_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "builtinShadow"
|
||||
info.Tags = []string{"style", "opinionated"}
|
||||
info.Summary = "Detects when predeclared identifiers shadowed in assignments"
|
||||
info.Before = `len := 10`
|
||||
info.After = `length := 10`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForLocalDef(&builtinShadowChecker{ctx: ctx}, ctx.TypesInfo)
|
||||
})
|
||||
}
|
||||
|
||||
type builtinShadowChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *builtinShadowChecker) VisitLocalDef(name astwalk.Name, _ ast.Expr) {
|
||||
if isBuiltin(name.ID.Name) {
|
||||
c.warn(name.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *builtinShadowChecker) warn(ident *ast.Ident) {
|
||||
c.ctx.Warn(ident, "shadowing of predeclared identifier: %s", ident)
|
||||
}
|
49
tests/tools/vendor/github.com/go-critic/go-critic/checkers/captLocal_checker.go
generated
vendored
Normal file
49
tests/tools/vendor/github.com/go-critic/go-critic/checkers/captLocal_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "captLocal"
|
||||
info.Tags = []string{"style"}
|
||||
info.Params = lintpack.CheckerParams{
|
||||
"paramsOnly": {
|
||||
Value: true,
|
||||
Usage: "whether to restrict checker to params only",
|
||||
},
|
||||
}
|
||||
info.Summary = "Detects capitalized names for local variables"
|
||||
info.Before = `func f(IN int, OUT *int) (ERR error) {}`
|
||||
info.After = `func f(in int, out *int) (err error) {}`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
c := &captLocalChecker{ctx: ctx}
|
||||
c.paramsOnly = info.Params.Bool("paramsOnly")
|
||||
return astwalk.WalkerForLocalDef(c, ctx.TypesInfo)
|
||||
})
|
||||
}
|
||||
|
||||
type captLocalChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
paramsOnly bool
|
||||
}
|
||||
|
||||
func (c *captLocalChecker) VisitLocalDef(def astwalk.Name, _ ast.Expr) {
|
||||
if c.paramsOnly && def.Kind != astwalk.NameParam {
|
||||
return
|
||||
}
|
||||
if ast.IsExported(def.ID.Name) {
|
||||
c.warn(def.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *captLocalChecker) warn(id ast.Node) {
|
||||
c.ctx.Warn(id, "`%s' should not be capitalized", id)
|
||||
}
|
88
tests/tools/vendor/github.com/go-critic/go-critic/checkers/caseOrder_checker.go
generated
vendored
Normal file
88
tests/tools/vendor/github.com/go-critic/go-critic/checkers/caseOrder_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "caseOrder"
|
||||
info.Tags = []string{"diagnostic"}
|
||||
info.Summary = "Detects erroneous case order inside switch statements"
|
||||
info.Before = `
|
||||
switch x.(type) {
|
||||
case ast.Expr:
|
||||
fmt.Println("expr")
|
||||
case *ast.BasicLit:
|
||||
fmt.Println("basic lit") // Never executed
|
||||
}`
|
||||
info.After = `
|
||||
switch x.(type) {
|
||||
case *ast.BasicLit:
|
||||
fmt.Println("basic lit") // Now reachable
|
||||
case ast.Expr:
|
||||
fmt.Println("expr")
|
||||
}`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForStmt(&caseOrderChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type caseOrderChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *caseOrderChecker) VisitStmt(stmt ast.Stmt) {
|
||||
switch stmt := stmt.(type) {
|
||||
case *ast.TypeSwitchStmt:
|
||||
c.checkTypeSwitch(stmt)
|
||||
case *ast.SwitchStmt:
|
||||
c.checkSwitch(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *caseOrderChecker) checkTypeSwitch(s *ast.TypeSwitchStmt) {
|
||||
type ifaceType struct {
|
||||
node ast.Node
|
||||
typ *types.Interface
|
||||
}
|
||||
var ifaces []ifaceType // Interfaces seen so far
|
||||
for _, cc := range s.Body.List {
|
||||
cc := cc.(*ast.CaseClause)
|
||||
for _, x := range cc.List {
|
||||
typ := c.ctx.TypesInfo.TypeOf(x)
|
||||
if typ == nil {
|
||||
c.warnTypeImpl(cc, x)
|
||||
return
|
||||
}
|
||||
for _, iface := range ifaces {
|
||||
if types.Implements(typ, iface.typ) {
|
||||
c.warnTypeSwitch(cc, x, iface.node)
|
||||
break
|
||||
}
|
||||
}
|
||||
if iface, ok := typ.Underlying().(*types.Interface); ok {
|
||||
ifaces = append(ifaces, ifaceType{node: x, typ: iface})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *caseOrderChecker) warnTypeSwitch(cause, concrete, iface ast.Node) {
|
||||
c.ctx.Warn(cause, "case %s must go before the %s case", concrete, iface)
|
||||
}
|
||||
|
||||
func (c *caseOrderChecker) warnTypeImpl(cause, concrete ast.Node) {
|
||||
c.ctx.Warn(cause, "type is not defined %s", concrete)
|
||||
}
|
||||
|
||||
func (c *caseOrderChecker) checkSwitch(s *ast.SwitchStmt) {
|
||||
// TODO(Quasilyte): can handle expression cases that overlap.
|
||||
// Cases that have narrower value range should go before wider ones.
|
||||
}
|
10
tests/tools/vendor/github.com/go-critic/go-critic/checkers/checkers.go
generated
vendored
Normal file
10
tests/tools/vendor/github.com/go-critic/go-critic/checkers/checkers.go
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
// Package checkers is a gocritic linter main checkers collection.
|
||||
package checkers
|
||||
|
||||
import (
|
||||
"github.com/go-lintpack/lintpack"
|
||||
)
|
||||
|
||||
var collection = &lintpack.CheckerCollection{
|
||||
URL: "https://github.com/go-critic/go-critic/checkers",
|
||||
}
|
61
tests/tools/vendor/github.com/go-critic/go-critic/checkers/codegenComment_checker.go
generated
vendored
Normal file
61
tests/tools/vendor/github.com/go-critic/go-critic/checkers/codegenComment_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "codegenComment"
|
||||
info.Tags = []string{"diagnostic", "experimental"}
|
||||
info.Summary = "Detects malformed 'code generated' file comments"
|
||||
info.Before = `// This file was automatically generated by foogen`
|
||||
info.After = `// Code generated by foogen. DO NOT EDIT.`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
patterns := []string{
|
||||
"this (?:file|code) (?:was|is) auto(?:matically)? generated",
|
||||
"this (?:file|code) (?:was|is) generated automatically",
|
||||
"this (?:file|code) (?:was|is) generated by",
|
||||
"this (?:file|code) (?:was|is) (?:auto(?:matically)? )?generated",
|
||||
"this (?:file|code) (?:was|is) generated",
|
||||
"code in this file (?:was|is) auto(?:matically)? generated",
|
||||
"generated (?:file|code) - do not edit",
|
||||
// TODO(Quasilyte): more of these.
|
||||
}
|
||||
re := regexp.MustCompile("(?i)" + strings.Join(patterns, "|"))
|
||||
return &codegenCommentChecker{
|
||||
ctx: ctx,
|
||||
badCommentRE: re,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type codegenCommentChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
badCommentRE *regexp.Regexp
|
||||
}
|
||||
|
||||
func (c *codegenCommentChecker) WalkFile(f *ast.File) {
|
||||
if f.Doc == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, comment := range f.Doc.List {
|
||||
if c.badCommentRE.MatchString(comment.Text) {
|
||||
c.warn(comment)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *codegenCommentChecker) warn(cause ast.Node) {
|
||||
c.ctx.Warn(cause, "comment should match `Code generated .* DO NOT EDIT.` regexp")
|
||||
}
|
77
tests/tools/vendor/github.com/go-critic/go-critic/checkers/commentFormatting_checker.go
generated
vendored
Normal file
77
tests/tools/vendor/github.com/go-critic/go-critic/checkers/commentFormatting_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "commentFormatting"
|
||||
info.Tags = []string{"style", "experimental"}
|
||||
info.Summary = "Detects comments with non-idiomatic formatting"
|
||||
info.Before = `//This is a comment`
|
||||
info.After = `// This is a comment`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
parts := []string{
|
||||
`^//\w+:.*$`, //key: value
|
||||
`^//nolint$`, //nolint
|
||||
`^//line /.*:\d+`, //line /path/to/file:123
|
||||
}
|
||||
pat := "(?m)" + strings.Join(parts, "|")
|
||||
pragmaRE := regexp.MustCompile(pat)
|
||||
return astwalk.WalkerForComment(&commentFormattingChecker{
|
||||
ctx: ctx,
|
||||
pragmaRE: pragmaRE,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type commentFormattingChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
pragmaRE *regexp.Regexp
|
||||
}
|
||||
|
||||
func (c *commentFormattingChecker) VisitComment(cg *ast.CommentGroup) {
|
||||
if strings.HasPrefix(cg.List[0].Text, "/*") {
|
||||
return
|
||||
}
|
||||
for _, comment := range cg.List {
|
||||
if len(comment.Text) <= len("// ") {
|
||||
continue
|
||||
}
|
||||
if c.pragmaRE.MatchString(comment.Text) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Make a decision based on a first comment text rune.
|
||||
r, _ := utf8.DecodeRuneInString(comment.Text[len("//"):])
|
||||
if !c.specialChar(r) && !unicode.IsSpace(r) {
|
||||
c.warn(cg)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *commentFormattingChecker) specialChar(r rune) bool {
|
||||
// Permitted list to avoid false-positives.
|
||||
switch r {
|
||||
case '+', '-', '#', '!':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *commentFormattingChecker) warn(cg *ast.CommentGroup) {
|
||||
c.ctx.Warn(cg, "put a space between `//` and comment text")
|
||||
}
|
157
tests/tools/vendor/github.com/go-critic/go-critic/checkers/commentedOutCode_checker.go
generated
vendored
Normal file
157
tests/tools/vendor/github.com/go-critic/go-critic/checkers/commentedOutCode_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,157 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/strparse"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "commentedOutCode"
|
||||
info.Tags = []string{"diagnostic", "experimental"}
|
||||
info.Summary = "Detects commented-out code inside function bodies"
|
||||
info.Before = `
|
||||
// fmt.Println("Debugging hard")
|
||||
foo(1, 2)`
|
||||
info.After = `foo(1, 2)`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForLocalComment(&commentedOutCodeChecker{
|
||||
ctx: ctx,
|
||||
notQuiteFuncCall: regexp.MustCompile(`\w+\s+\([^)]*\)\s*$`),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type commentedOutCodeChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
fn *ast.FuncDecl
|
||||
|
||||
notQuiteFuncCall *regexp.Regexp
|
||||
}
|
||||
|
||||
func (c *commentedOutCodeChecker) EnterFunc(fn *ast.FuncDecl) bool {
|
||||
c.fn = fn // Need to store current function inside checker context
|
||||
return fn.Body != nil
|
||||
}
|
||||
|
||||
func (c *commentedOutCodeChecker) VisitLocalComment(cg *ast.CommentGroup) {
|
||||
s := cg.Text() // Collect text once
|
||||
|
||||
// We do multiple heuristics to avoid false positives.
|
||||
// Many things can be improved here.
|
||||
|
||||
markers := []string{
|
||||
"TODO", // TODO comments with code are permitted.
|
||||
|
||||
// "http://" is interpreted as a label with comment.
|
||||
// There are other protocols we might want to include.
|
||||
"http://",
|
||||
"https://",
|
||||
|
||||
"e.g. ", // Clearly not a "selector expr" (mostly due to extra space)
|
||||
}
|
||||
for _, m := range markers {
|
||||
if strings.Contains(s, m) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Some very short comment that can be skipped.
|
||||
// Usually triggering on these results in false positive.
|
||||
// Unless there is a very popular call like print/println.
|
||||
cond := len(s) < len("quite too short") &&
|
||||
!strings.Contains(s, "print") &&
|
||||
!strings.Contains(s, "fmt.") &&
|
||||
!strings.Contains(s, "log.")
|
||||
if cond {
|
||||
return
|
||||
}
|
||||
|
||||
// Almost looks like a commented-out function call,
|
||||
// but there is a whitespace between function name and
|
||||
// parameters list. Skip these to avoid false positives.
|
||||
if c.notQuiteFuncCall.MatchString(s) {
|
||||
return
|
||||
}
|
||||
|
||||
stmt := strparse.Stmt(s)
|
||||
|
||||
if c.isPermittedStmt(stmt) {
|
||||
return
|
||||
}
|
||||
|
||||
if stmt != strparse.BadStmt {
|
||||
c.warn(cg)
|
||||
return
|
||||
}
|
||||
|
||||
// Don't try to parse one-liner as block statement
|
||||
if len(cg.List) == 1 && !strings.Contains(s, "\n") {
|
||||
return
|
||||
}
|
||||
|
||||
// Some attempts to avoid false positives.
|
||||
if c.skipBlock(s) {
|
||||
return
|
||||
}
|
||||
|
||||
// Add braces to make block statement from
|
||||
// multiple statements.
|
||||
stmt = strparse.Stmt(fmt.Sprintf("{ %s }", s))
|
||||
|
||||
if stmt, ok := stmt.(*ast.BlockStmt); ok && len(stmt.List) != 0 {
|
||||
c.warn(cg)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *commentedOutCodeChecker) skipBlock(s string) bool {
|
||||
lines := strings.Split(s, "\n") // There is at least 1 line, that's invariant
|
||||
|
||||
// Special example test block.
|
||||
if isExampleTestFunc(c.fn) && strings.Contains(lines[0], "Output:") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *commentedOutCodeChecker) isPermittedStmt(stmt ast.Stmt) bool {
|
||||
switch stmt := stmt.(type) {
|
||||
case *ast.ExprStmt:
|
||||
return c.isPermittedExpr(stmt.X)
|
||||
case *ast.LabeledStmt:
|
||||
return c.isPermittedStmt(stmt.Stmt)
|
||||
case *ast.DeclStmt:
|
||||
decl := stmt.Decl.(*ast.GenDecl)
|
||||
return decl.Tok == token.TYPE
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *commentedOutCodeChecker) isPermittedExpr(x ast.Expr) bool {
|
||||
// Permit anything except expressions that can be used
|
||||
// with complete result discarding.
|
||||
switch x := x.(type) {
|
||||
case *ast.CallExpr:
|
||||
return false
|
||||
case *ast.UnaryExpr:
|
||||
// "<-" channel receive is not permitted.
|
||||
return x.Op != token.ARROW
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (c *commentedOutCodeChecker) warn(cause ast.Node) {
|
||||
c.ctx.Warn(cause, "may want to remove commented-out code")
|
||||
}
|
76
tests/tools/vendor/github.com/go-critic/go-critic/checkers/commentedOutImport_checker.go
generated
vendored
Normal file
76
tests/tools/vendor/github.com/go-critic/go-critic/checkers/commentedOutImport_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,76 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"regexp"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "commentedOutImport"
|
||||
info.Tags = []string{"style", "experimental"}
|
||||
info.Summary = "Detects commented-out imports"
|
||||
info.Before = `
|
||||
import (
|
||||
"fmt"
|
||||
//"os"
|
||||
)`
|
||||
info.After = `
|
||||
import (
|
||||
"fmt"
|
||||
)`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
const pattern = `(?m)^(?://|/\*)?\s*"([a-zA-Z0-9_/]+)"\s*(?:\*/)?$`
|
||||
return &commentedOutImportChecker{
|
||||
ctx: ctx,
|
||||
importStringRE: regexp.MustCompile(pattern),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type commentedOutImportChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
importStringRE *regexp.Regexp
|
||||
}
|
||||
|
||||
func (c *commentedOutImportChecker) WalkFile(f *ast.File) {
|
||||
// TODO(Quasilyte): handle commented-out import spec,
|
||||
// for example: // import "errors".
|
||||
|
||||
for _, decl := range f.Decls {
|
||||
decl, ok := decl.(*ast.GenDecl)
|
||||
if !ok || decl.Tok != token.IMPORT {
|
||||
// Import decls can only be in the beginning of the file.
|
||||
// If we've met some other decl, there will be no more
|
||||
// import decls.
|
||||
break
|
||||
}
|
||||
|
||||
// Find comments inside this import decl span.
|
||||
for _, cg := range f.Comments {
|
||||
if cg.Pos() > decl.Rparen {
|
||||
break // Below the decl, stop.
|
||||
}
|
||||
if cg.Pos() < decl.Lparen {
|
||||
continue // Before the decl, skip.
|
||||
}
|
||||
|
||||
for _, comment := range cg.List {
|
||||
for _, m := range c.importStringRE.FindAllStringSubmatch(comment.Text, -1) {
|
||||
c.warn(comment, m[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *commentedOutImportChecker) warn(cause ast.Node, path string) {
|
||||
c.ctx.Warn(cause, "remove commented-out %q import", path)
|
||||
}
|
65
tests/tools/vendor/github.com/go-critic/go-critic/checkers/defaultCaseOrder_checker.go
generated
vendored
Normal file
65
tests/tools/vendor/github.com/go-critic/go-critic/checkers/defaultCaseOrder_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "defaultCaseOrder"
|
||||
info.Tags = []string{"style"}
|
||||
info.Summary = "Detects when default case in switch isn't on 1st or last position"
|
||||
info.Before = `
|
||||
switch {
|
||||
case x > y:
|
||||
// ...
|
||||
default: // <- not the best position
|
||||
// ...
|
||||
case x == 10:
|
||||
// ...
|
||||
}`
|
||||
info.After = `
|
||||
switch {
|
||||
case x > y:
|
||||
// ...
|
||||
case x == 10:
|
||||
// ...
|
||||
default: // <- last case (could also be the first one)
|
||||
// ...
|
||||
}`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForStmt(&defaultCaseOrderChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type defaultCaseOrderChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *defaultCaseOrderChecker) VisitStmt(stmt ast.Stmt) {
|
||||
swtch, ok := stmt.(*ast.SwitchStmt)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for i, stmt := range swtch.Body.List {
|
||||
caseStmt, ok := stmt.(*ast.CaseClause)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// is `default` case
|
||||
if caseStmt.List == nil {
|
||||
if i != 0 && i != len(swtch.Body.List)-1 {
|
||||
c.warn(caseStmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *defaultCaseOrderChecker) warn(cause *ast.CaseClause) {
|
||||
c.ctx.Warn(cause, "consider to make `default` case as first or as last case")
|
||||
}
|
149
tests/tools/vendor/github.com/go-critic/go-critic/checkers/deprecatedComment_checker.go
generated
vendored
Normal file
149
tests/tools/vendor/github.com/go-critic/go-critic/checkers/deprecatedComment_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,149 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "deprecatedComment"
|
||||
info.Tags = []string{"diagnostic", "experimental"}
|
||||
info.Summary = "Detects malformed 'deprecated' doc-comments"
|
||||
info.Before = `
|
||||
// deprecated, use FuncNew instead
|
||||
func FuncOld() int`
|
||||
info.After = `
|
||||
// Deprecated: use FuncNew instead
|
||||
func FuncOld() int`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
c := &deprecatedCommentChecker{ctx: ctx}
|
||||
|
||||
c.commonPatterns = []*regexp.Regexp{
|
||||
regexp.MustCompile(`(?i)this (?:function|type) is deprecated`),
|
||||
regexp.MustCompile(`(?i)deprecated[.!]? use \S* instead`),
|
||||
regexp.MustCompile(`(?i)\[\[deprecated\]\].*`),
|
||||
regexp.MustCompile(`(?i)note: deprecated\b.*`),
|
||||
regexp.MustCompile(`(?i)deprecated in.*`),
|
||||
// TODO(quasilyte): more of these?
|
||||
}
|
||||
|
||||
// TODO(quasilyte): may want to generate this list programmatically.
|
||||
//
|
||||
// TODO(quasilyte): currently it only handles a single missing letter.
|
||||
// Might want to handle other kinds of common misspell/typo kinds.
|
||||
c.commonTypos = []string{
|
||||
"Dprecated: ",
|
||||
"Derecated: ",
|
||||
"Depecated: ",
|
||||
"Deprcated: ",
|
||||
"Depreated: ",
|
||||
"Deprected: ",
|
||||
"Deprecaed: ",
|
||||
"Deprecatd: ",
|
||||
"Deprecate: ",
|
||||
"Derpecate: ",
|
||||
"Derpecated: ",
|
||||
"Depreacted: ",
|
||||
}
|
||||
for i := range c.commonTypos {
|
||||
c.commonTypos[i] = strings.ToUpper(c.commonTypos[i])
|
||||
}
|
||||
|
||||
return astwalk.WalkerForDocComment(c)
|
||||
})
|
||||
}
|
||||
|
||||
type deprecatedCommentChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
commonPatterns []*regexp.Regexp
|
||||
commonTypos []string
|
||||
}
|
||||
|
||||
func (c *deprecatedCommentChecker) VisitDocComment(doc *ast.CommentGroup) {
|
||||
// There are 3 accepted forms of deprecation comments:
|
||||
//
|
||||
// 1. inline, that can't be handled with a DocCommentVisitor.
|
||||
// Note that "Deprecated: " may not even be the comment prefix there.
|
||||
// Example: "The line number in the input. Deprecated: Kept for compatibility."
|
||||
// TODO(quasilyte): fix it.
|
||||
//
|
||||
// 2. Longer form-1. It's a doc-comment that only contains "deprecation" notice.
|
||||
//
|
||||
// 3. Like form-2, but may also include doc-comment text.
|
||||
// Distinguished by an empty line.
|
||||
//
|
||||
// See https://github.com/golang/go/issues/10909#issuecomment-136492606.
|
||||
//
|
||||
// It's desirable to see how people make mistakes with the format,
|
||||
// this is why there is currently no special treatment for these cases.
|
||||
// TODO(quasilyte): do more audits and grow the negative tests suite.
|
||||
//
|
||||
// TODO(quasilyte): there are also multi-line deprecation comments.
|
||||
|
||||
for _, comment := range doc.List {
|
||||
if strings.HasPrefix(comment.Text, "/*") {
|
||||
// TODO(quasilyte): handle multi-line doc comments.
|
||||
continue
|
||||
}
|
||||
l := comment.Text[len("//"):]
|
||||
if len(l) < len("Deprecated: ") {
|
||||
continue
|
||||
}
|
||||
l = strings.TrimSpace(l)
|
||||
|
||||
// Check whether someone messed up with a prefix casing.
|
||||
upcase := strings.ToUpper(l)
|
||||
if strings.HasPrefix(upcase, "DEPRECATED: ") && !strings.HasPrefix(l, "Deprecated: ") {
|
||||
c.warnCasing(comment, l)
|
||||
return
|
||||
}
|
||||
|
||||
// Check is someone used comma instead of a colon.
|
||||
if strings.HasPrefix(l, "Deprecated, ") {
|
||||
c.warnComma(comment)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for other commonly used patterns.
|
||||
for _, pat := range c.commonPatterns {
|
||||
if pat.MatchString(l) {
|
||||
c.warnPattern(comment)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Detect some simple typos.
|
||||
for _, prefixWithTypo := range c.commonTypos {
|
||||
if strings.HasPrefix(upcase, prefixWithTypo) {
|
||||
c.warnTypo(comment, l)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *deprecatedCommentChecker) warnCasing(cause ast.Node, line string) {
|
||||
prefix := line[:len("DEPRECATED: ")]
|
||||
c.ctx.Warn(cause, "use `Deprecated: ` (note the casing) instead of `%s`", prefix)
|
||||
}
|
||||
|
||||
func (c *deprecatedCommentChecker) warnPattern(cause ast.Node) {
|
||||
c.ctx.Warn(cause, "the proper format is `Deprecated: <text>`")
|
||||
}
|
||||
|
||||
func (c *deprecatedCommentChecker) warnComma(cause ast.Node) {
|
||||
c.ctx.Warn(cause, "use `:` instead of `,` in `Deprecated, `")
|
||||
}
|
||||
|
||||
func (c *deprecatedCommentChecker) warnTypo(cause ast.Node, line string) {
|
||||
word := strings.Split(line, ":")[0]
|
||||
c.ctx.Warn(cause, "typo in `%s`; should be `Deprecated`", word)
|
||||
}
|
95
tests/tools/vendor/github.com/go-critic/go-critic/checkers/docStub_checker.go
generated
vendored
Normal file
95
tests/tools/vendor/github.com/go-critic/go-critic/checkers/docStub_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "docStub"
|
||||
info.Tags = []string{"style", "experimental"}
|
||||
info.Summary = "Detects comments that silence go lint complaints about doc-comment"
|
||||
info.Before = `
|
||||
// Foo ...
|
||||
func Foo() {
|
||||
}`
|
||||
info.After = `
|
||||
// (A) - remove the doc-comment stub
|
||||
func Foo() {}
|
||||
// (B) - replace it with meaningful comment
|
||||
// Foo is a demonstration-only function.
|
||||
func Foo() {}`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
re := `(?i)^\.\.\.$|^\.$|^xxx\.?$|^whatever\.?$`
|
||||
c := &docStubChecker{
|
||||
ctx: ctx,
|
||||
stubCommentRE: regexp.MustCompile(re),
|
||||
}
|
||||
return c
|
||||
})
|
||||
}
|
||||
|
||||
type docStubChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
stubCommentRE *regexp.Regexp
|
||||
}
|
||||
|
||||
func (c *docStubChecker) WalkFile(f *ast.File) {
|
||||
for _, decl := range f.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.FuncDecl:
|
||||
c.visitDoc(decl, decl.Name, decl.Doc, false)
|
||||
case *ast.GenDecl:
|
||||
if decl.Tok != token.TYPE {
|
||||
continue
|
||||
}
|
||||
if len(decl.Specs) == 1 {
|
||||
spec := decl.Specs[0].(*ast.TypeSpec)
|
||||
// Only 1 spec, use doc from the decl itself.
|
||||
c.visitDoc(spec, spec.Name, decl.Doc, true)
|
||||
}
|
||||
// N specs, use per-spec doc.
|
||||
for _, spec := range decl.Specs {
|
||||
spec := spec.(*ast.TypeSpec)
|
||||
c.visitDoc(spec, spec.Name, spec.Doc, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *docStubChecker) visitDoc(decl ast.Node, sym *ast.Ident, doc *ast.CommentGroup, article bool) {
|
||||
if !sym.IsExported() || doc == nil {
|
||||
return
|
||||
}
|
||||
line := strings.TrimSpace(doc.List[0].Text[len("//"):])
|
||||
if article {
|
||||
// Skip optional article.
|
||||
for _, a := range []string{"The ", "An ", "A "} {
|
||||
if strings.HasPrefix(line, a) {
|
||||
line = line[len(a):]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !strings.HasPrefix(line, sym.Name) {
|
||||
return
|
||||
}
|
||||
line = strings.TrimSpace(line[len(sym.Name):])
|
||||
// Now try to detect the "stub" part.
|
||||
if c.stubCommentRE.MatchString(line) {
|
||||
c.warn(decl)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *docStubChecker) warn(cause ast.Node) {
|
||||
c.ctx.Warn(cause, "silencing go lint doc-comment warnings is unadvised")
|
||||
}
|
130
tests/tools/vendor/github.com/go-critic/go-critic/checkers/dupArg_checker.go
generated
vendored
Normal file
130
tests/tools/vendor/github.com/go-critic/go-critic/checkers/dupArg_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,130 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
"github.com/go-toolsmith/astequal"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "dupArg"
|
||||
info.Tags = []string{"diagnostic"}
|
||||
info.Summary = "Detects suspicious duplicated arguments"
|
||||
info.Before = `copy(dst, dst)`
|
||||
info.After = `copy(dst, src)`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
c := &dupArgChecker{ctx: ctx}
|
||||
// newMatcherFunc returns a function that matches a call if
|
||||
// args[xIndex] and args[yIndex] are equal.
|
||||
newMatcherFunc := func(xIndex, yIndex int) func(*ast.CallExpr) bool {
|
||||
return func(call *ast.CallExpr) bool {
|
||||
x := call.Args[xIndex]
|
||||
y := call.Args[yIndex]
|
||||
return astequal.Expr(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
// m maps pattern string to a matching function.
|
||||
// String patterns are used for documentation purposes (readability).
|
||||
m := map[string]func(*ast.CallExpr) bool{
|
||||
"(x, x, ...)": newMatcherFunc(0, 1),
|
||||
"(x, _, x, ...)": newMatcherFunc(0, 2),
|
||||
"(_, x, x, ...)": newMatcherFunc(1, 2),
|
||||
}
|
||||
|
||||
// TODO(quasilyte): handle x.Equal(x) cases.
|
||||
// Example: *math/Big.Int.Cmp method.
|
||||
|
||||
// TODO(quasilyte): more perky mode that will also
|
||||
// report things like io.Copy(x, x).
|
||||
// Probably safe thing to do even without that option
|
||||
// if `x` is not interface (requires type checks
|
||||
// that are not incorporated into this checker yet).
|
||||
|
||||
c.matchers = map[string]func(*ast.CallExpr) bool{
|
||||
"copy": m["(x, x, ...)"],
|
||||
|
||||
"math.Max": m["(x, x, ...)"],
|
||||
"math.Min": m["(x, x, ...)"],
|
||||
|
||||
"reflect.Copy": m["(x, x, ...)"],
|
||||
"reflect.DeepEqual": m["(x, x, ...)"],
|
||||
|
||||
"strings.Contains": m["(x, x, ...)"],
|
||||
"strings.Compare": m["(x, x, ...)"],
|
||||
"strings.EqualFold": m["(x, x, ...)"],
|
||||
"strings.HasPrefix": m["(x, x, ...)"],
|
||||
"strings.HasSuffix": m["(x, x, ...)"],
|
||||
"strings.Index": m["(x, x, ...)"],
|
||||
"strings.LastIndex": m["(x, x, ...)"],
|
||||
"strings.Split": m["(x, x, ...)"],
|
||||
"strings.SplitAfter": m["(x, x, ...)"],
|
||||
"strings.SplitAfterN": m["(x, x, ...)"],
|
||||
"strings.SplitN": m["(x, x, ...)"],
|
||||
"strings.Replace": m["(_, x, x, ...)"],
|
||||
"strings.ReplaceAll": m["(_, x, x, ...)"],
|
||||
|
||||
"bytes.Contains": m["(x, x, ...)"],
|
||||
"bytes.Compare": m["(x, x, ...)"],
|
||||
"bytes.Equal": m["(x, x, ...)"],
|
||||
"bytes.EqualFold": m["(x, x, ...)"],
|
||||
"bytes.HasPrefix": m["(x, x, ...)"],
|
||||
"bytes.HasSuffix": m["(x, x, ...)"],
|
||||
"bytes.Index": m["(x, x, ...)"],
|
||||
"bytes.LastIndex": m["(x, x, ...)"],
|
||||
"bytes.Split": m["(x, x, ...)"],
|
||||
"bytes.SplitAfter": m["(x, x, ...)"],
|
||||
"bytes.SplitAfterN": m["(x, x, ...)"],
|
||||
"bytes.SplitN": m["(x, x, ...)"],
|
||||
"bytes.Replace": m["(_, x, x, ...)"],
|
||||
"bytes.ReplaceAll": m["(_, x, x, ...)"],
|
||||
|
||||
"types.Identical": m["(x, x, ...)"],
|
||||
"types.IdenticalIgnoreTags": m["(x, x, ...)"],
|
||||
|
||||
"draw.Draw": m["(x, _, x, ...)"],
|
||||
|
||||
// TODO(quasilyte): more of these.
|
||||
}
|
||||
return astwalk.WalkerForExpr(c)
|
||||
})
|
||||
}
|
||||
|
||||
type dupArgChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
matchers map[string]func(*ast.CallExpr) bool
|
||||
}
|
||||
|
||||
func (c *dupArgChecker) VisitExpr(expr ast.Expr) {
|
||||
call, ok := expr.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(quasilyte): this kind of check is needed in multiple
|
||||
// places and the code is somewhat duplicated around.
|
||||
// We probably need to stop using qualifiedName for non-experimental checkers.
|
||||
if calledExpr, ok := call.Fun.(*ast.SelectorExpr); ok {
|
||||
obj, ok := c.ctx.TypesInfo.ObjectOf(astcast.ToIdent(calledExpr.X)).(*types.PkgName)
|
||||
if !ok || !isStdlibPkg(obj.Imported()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
m := c.matchers[qualifiedName(call.Fun)]
|
||||
if m != nil && m(call) {
|
||||
c.warn(call)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dupArgChecker) warn(cause ast.Node) {
|
||||
c.ctx.Warn(cause, "suspicious duplicated args in `%s`", cause)
|
||||
}
|
58
tests/tools/vendor/github.com/go-critic/go-critic/checkers/dupBranchBody_checker.go
generated
vendored
Normal file
58
tests/tools/vendor/github.com/go-critic/go-critic/checkers/dupBranchBody_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astequal"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "dupBranchBody"
|
||||
info.Tags = []string{"diagnostic"}
|
||||
info.Summary = "Detects duplicated branch bodies inside conditional statements"
|
||||
info.Before = `
|
||||
if cond {
|
||||
println("cond=true")
|
||||
} else {
|
||||
println("cond=true")
|
||||
}`
|
||||
info.After = `
|
||||
if cond {
|
||||
println("cond=true")
|
||||
} else {
|
||||
println("cond=false")
|
||||
}`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForStmt(&dupBranchBodyChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type dupBranchBodyChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *dupBranchBodyChecker) VisitStmt(stmt ast.Stmt) {
|
||||
// TODO(quasilyte): extend to check switch statements as well.
|
||||
// Should be very careful with type switches.
|
||||
|
||||
if stmt, ok := stmt.(*ast.IfStmt); ok {
|
||||
c.checkIf(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dupBranchBodyChecker) checkIf(stmt *ast.IfStmt) {
|
||||
thenBody := stmt.Body
|
||||
elseBody, ok := stmt.Else.(*ast.BlockStmt)
|
||||
if ok && astequal.Stmt(thenBody, elseBody) {
|
||||
c.warnIf(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dupBranchBodyChecker) warnIf(cause ast.Node) {
|
||||
c.ctx.Warn(cause, "both branches in if statement has same body")
|
||||
}
|
57
tests/tools/vendor/github.com/go-critic/go-critic/checkers/dupCase_checker.go
generated
vendored
Normal file
57
tests/tools/vendor/github.com/go-critic/go-critic/checkers/dupCase_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-critic/go-critic/checkers/internal/lintutil"
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "dupCase"
|
||||
info.Tags = []string{"diagnostic"}
|
||||
info.Summary = "Detects duplicated case clauses inside switch statements"
|
||||
info.Before = `
|
||||
switch x {
|
||||
case ys[0], ys[1], ys[2], ys[0], ys[4]:
|
||||
}`
|
||||
info.After = `
|
||||
switch x {
|
||||
case ys[0], ys[1], ys[2], ys[3], ys[4]:
|
||||
}`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForStmt(&dupCaseChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type dupCaseChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
astSet lintutil.AstSet
|
||||
}
|
||||
|
||||
func (c *dupCaseChecker) VisitStmt(stmt ast.Stmt) {
|
||||
if stmt, ok := stmt.(*ast.SwitchStmt); ok {
|
||||
c.checkSwitch(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dupCaseChecker) checkSwitch(stmt *ast.SwitchStmt) {
|
||||
c.astSet.Clear()
|
||||
for i := range stmt.Body.List {
|
||||
cc := stmt.Body.List[i].(*ast.CaseClause)
|
||||
for _, x := range cc.List {
|
||||
if !c.astSet.Insert(x) {
|
||||
c.warn(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dupCaseChecker) warn(cause ast.Node) {
|
||||
c.ctx.Warn(cause, "'case %s' is duplicated", cause)
|
||||
}
|
63
tests/tools/vendor/github.com/go-critic/go-critic/checkers/dupImports_checker.go
generated
vendored
Normal file
63
tests/tools/vendor/github.com/go-critic/go-critic/checkers/dupImports_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "dupImport"
|
||||
info.Tags = []string{"style", "experimental"}
|
||||
info.Summary = "Detects multiple imports of the same package under different aliases"
|
||||
info.Before = `
|
||||
import (
|
||||
"fmt"
|
||||
priting "fmt" // Imported the second time
|
||||
)`
|
||||
info.After = `
|
||||
import(
|
||||
"fmt"
|
||||
)`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return &dupImportChecker{ctx: ctx}
|
||||
})
|
||||
}
|
||||
|
||||
type dupImportChecker struct {
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *dupImportChecker) WalkFile(f *ast.File) {
|
||||
imports := make(map[string][]*ast.ImportSpec)
|
||||
for _, importDcl := range f.Imports {
|
||||
pkg := importDcl.Path.Value
|
||||
imports[pkg] = append(imports[pkg], importDcl)
|
||||
}
|
||||
|
||||
for _, importList := range imports {
|
||||
if len(importList) == 1 {
|
||||
continue
|
||||
}
|
||||
c.warn(importList)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dupImportChecker) warn(importList []*ast.ImportSpec) {
|
||||
msg := fmt.Sprintf("package is imported %d times under different aliases on lines", len(importList))
|
||||
for idx, importDcl := range importList {
|
||||
switch {
|
||||
case idx == len(importList)-1:
|
||||
msg += " and"
|
||||
case idx > 0:
|
||||
msg += ","
|
||||
}
|
||||
msg += fmt.Sprintf(" %d", c.ctx.FileSet.Position(importDcl.Pos()).Line)
|
||||
}
|
||||
for _, importDcl := range importList {
|
||||
c.ctx.Warn(importDcl, msg)
|
||||
}
|
||||
}
|
102
tests/tools/vendor/github.com/go-critic/go-critic/checkers/dupSubExpr_checker.go
generated
vendored
Normal file
102
tests/tools/vendor/github.com/go-critic/go-critic/checkers/dupSubExpr_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astequal"
|
||||
"github.com/go-toolsmith/typep"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "dupSubExpr"
|
||||
info.Tags = []string{"diagnostic"}
|
||||
info.Summary = "Detects suspicious duplicated sub-expressions"
|
||||
info.Before = `
|
||||
sort.Slice(xs, func(i, j int) bool {
|
||||
return xs[i].v < xs[i].v // Duplicated index
|
||||
})`
|
||||
info.After = `
|
||||
sort.Slice(xs, func(i, j int) bool {
|
||||
return xs[i].v < xs[j].v
|
||||
})`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
c := &dupSubExprChecker{ctx: ctx}
|
||||
|
||||
ops := []struct {
|
||||
op token.Token
|
||||
float bool // Whether float args require special care
|
||||
}{
|
||||
{op: token.LOR}, // x || x
|
||||
{op: token.LAND}, // x && x
|
||||
{op: token.OR}, // x | x
|
||||
{op: token.AND}, // x & x
|
||||
{op: token.XOR}, // x ^ x
|
||||
{op: token.LSS}, // x < x
|
||||
{op: token.GTR}, // x > x
|
||||
{op: token.AND_NOT}, // x &^ x
|
||||
{op: token.REM}, // x % x
|
||||
|
||||
{op: token.EQL, float: true}, // x == x
|
||||
{op: token.NEQ, float: true}, // x != x
|
||||
{op: token.LEQ, float: true}, // x <= x
|
||||
{op: token.GEQ, float: true}, // x >= x
|
||||
{op: token.QUO, float: true}, // x / x
|
||||
{op: token.SUB, float: true}, // x - x
|
||||
}
|
||||
|
||||
c.opSet = make(map[token.Token]bool)
|
||||
c.floatOpsSet = make(map[token.Token]bool)
|
||||
for _, opInfo := range ops {
|
||||
c.opSet[opInfo.op] = true
|
||||
if opInfo.float {
|
||||
c.floatOpsSet[opInfo.op] = true
|
||||
}
|
||||
}
|
||||
|
||||
return astwalk.WalkerForExpr(c)
|
||||
})
|
||||
}
|
||||
|
||||
type dupSubExprChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
// opSet is a set of binary operations that do not make
|
||||
// sense with duplicated (same) RHS and LHS.
|
||||
opSet map[token.Token]bool
|
||||
|
||||
floatOpsSet map[token.Token]bool
|
||||
}
|
||||
|
||||
func (c *dupSubExprChecker) VisitExpr(expr ast.Expr) {
|
||||
if expr, ok := expr.(*ast.BinaryExpr); ok {
|
||||
c.checkBinaryExpr(expr)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dupSubExprChecker) checkBinaryExpr(expr *ast.BinaryExpr) {
|
||||
if !c.opSet[expr.Op] {
|
||||
return
|
||||
}
|
||||
if c.resultIsFloat(expr.X) && c.floatOpsSet[expr.Op] {
|
||||
return
|
||||
}
|
||||
if typep.SideEffectFree(c.ctx.TypesInfo, expr) && c.opSet[expr.Op] && astequal.Expr(expr.X, expr.Y) {
|
||||
c.warn(expr)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dupSubExprChecker) resultIsFloat(expr ast.Expr) bool {
|
||||
typ, ok := c.ctx.TypesInfo.TypeOf(expr).(*types.Basic)
|
||||
return ok && typ.Info()&types.IsFloat != 0
|
||||
}
|
||||
|
||||
func (c *dupSubExprChecker) warn(cause *ast.BinaryExpr) {
|
||||
c.ctx.Warn(cause, "suspicious identical LHS and RHS for `%s` operator", cause.Op)
|
||||
}
|
71
tests/tools/vendor/github.com/go-critic/go-critic/checkers/elseif_checker.go
generated
vendored
Normal file
71
tests/tools/vendor/github.com/go-critic/go-critic/checkers/elseif_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "elseif"
|
||||
info.Tags = []string{"style"}
|
||||
info.Params = lintpack.CheckerParams{
|
||||
"skipBalanced": {
|
||||
Value: true,
|
||||
Usage: "whether to skip balanced if-else pairs",
|
||||
},
|
||||
}
|
||||
info.Summary = "Detects else with nested if statement that can be replaced with else-if"
|
||||
info.Before = `
|
||||
if cond1 {
|
||||
} else {
|
||||
if x := cond2; x {
|
||||
}
|
||||
}`
|
||||
info.After = `
|
||||
if cond1 {
|
||||
} else if x := cond2; x {
|
||||
}`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
c := &elseifChecker{ctx: ctx}
|
||||
c.skipBalanced = info.Params.Bool("skipBalanced")
|
||||
return astwalk.WalkerForStmt(c)
|
||||
})
|
||||
}
|
||||
|
||||
type elseifChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
skipBalanced bool
|
||||
}
|
||||
|
||||
func (c *elseifChecker) VisitStmt(stmt ast.Stmt) {
|
||||
if stmt, ok := stmt.(*ast.IfStmt); ok {
|
||||
elseBody, ok := stmt.Else.(*ast.BlockStmt)
|
||||
if !ok || len(elseBody.List) != 1 {
|
||||
return
|
||||
}
|
||||
innerIfStmt, ok := elseBody.List[0].(*ast.IfStmt)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
balanced := len(stmt.Body.List) == 1 &&
|
||||
astp.IsIfStmt(stmt.Body.List[0])
|
||||
if balanced && c.skipBalanced {
|
||||
return // Configured to skip balanced statements
|
||||
}
|
||||
if innerIfStmt.Else != nil {
|
||||
return
|
||||
}
|
||||
c.warn(stmt.Else)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *elseifChecker) warn(cause ast.Node) {
|
||||
c.ctx.Warn(cause, "can replace 'else {if cond {}}' with 'else if cond {}'")
|
||||
}
|
70
tests/tools/vendor/github.com/go-critic/go-critic/checkers/emptyFallthrough_checker.go
generated
vendored
Normal file
70
tests/tools/vendor/github.com/go-critic/go-critic/checkers/emptyFallthrough_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "emptyFallthrough"
|
||||
info.Tags = []string{"style", "experimental"}
|
||||
info.Summary = "Detects fallthrough that can be avoided by using multi case values"
|
||||
info.Before = `switch kind {
|
||||
case reflect.Int:
|
||||
fallthrough
|
||||
case reflect.Int32:
|
||||
return Int
|
||||
}`
|
||||
info.After = `switch kind {
|
||||
case reflect.Int, reflect.Int32:
|
||||
return Int
|
||||
}`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForStmt(&emptyFallthroughChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type emptyFallthroughChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *emptyFallthroughChecker) VisitStmt(stmt ast.Stmt) {
|
||||
ss, ok := stmt.(*ast.SwitchStmt)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
prevCaseDefault := false
|
||||
for i := len(ss.Body.List) - 1; i >= 0; i-- {
|
||||
if cc, ok := ss.Body.List[i].(*ast.CaseClause); ok {
|
||||
warn := false
|
||||
if len(cc.Body) == 1 {
|
||||
if bs, ok := cc.Body[0].(*ast.BranchStmt); ok && bs.Tok == token.FALLTHROUGH {
|
||||
warn = true
|
||||
if prevCaseDefault {
|
||||
c.warnDefault(bs)
|
||||
} else {
|
||||
c.warn(bs)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !warn {
|
||||
prevCaseDefault = cc.List == nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *emptyFallthroughChecker) warnDefault(cause ast.Node) {
|
||||
c.ctx.Warn(cause, "remove empty case containing only fallthrough to default case")
|
||||
}
|
||||
|
||||
func (c *emptyFallthroughChecker) warn(cause ast.Node) {
|
||||
c.ctx.Warn(cause, "replace empty case containing only fallthrough with expression list")
|
||||
}
|
58
tests/tools/vendor/github.com/go-critic/go-critic/checkers/emptyStringTest_checker.go
generated
vendored
Normal file
58
tests/tools/vendor/github.com/go-critic/go-critic/checkers/emptyStringTest_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
"github.com/go-toolsmith/astcopy"
|
||||
"github.com/go-toolsmith/typep"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "emptyStringTest"
|
||||
info.Tags = []string{"style", "experimental"}
|
||||
info.Summary = "Detects empty string checks that can be written more idiomatically"
|
||||
info.Before = `len(s) == 0`
|
||||
info.After = `s == ""`
|
||||
info.Note = "See https://dmitri.shuralyov.com/idiomatic-go#empty-string-check."
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForExpr(&emptyStringTestChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type emptyStringTestChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *emptyStringTestChecker) VisitExpr(e ast.Expr) {
|
||||
cmp := astcast.ToBinaryExpr(e)
|
||||
if cmp.Op != token.EQL && cmp.Op != token.NEQ {
|
||||
return
|
||||
}
|
||||
lenCall := astcast.ToCallExpr(cmp.X)
|
||||
if astcast.ToIdent(lenCall.Fun).Name != "len" {
|
||||
return
|
||||
}
|
||||
s := lenCall.Args[0]
|
||||
if !typep.HasStringProp(c.ctx.TypesInfo.TypeOf(s)) {
|
||||
return
|
||||
}
|
||||
zero := astcast.ToBasicLit(cmp.Y)
|
||||
if zero.Value != "0" {
|
||||
return
|
||||
}
|
||||
c.warn(cmp, s)
|
||||
}
|
||||
|
||||
func (c *emptyStringTestChecker) warn(cmp *ast.BinaryExpr, s ast.Expr) {
|
||||
suggest := astcopy.BinaryExpr(cmp)
|
||||
suggest.X = s
|
||||
suggest.Y = &ast.BasicLit{Value: `""`}
|
||||
c.ctx.Warn(cmp, "replace `%s` with `%s`", cmp, suggest)
|
||||
}
|
87
tests/tools/vendor/github.com/go-critic/go-critic/checkers/equalFold_checker.go
generated
vendored
Normal file
87
tests/tools/vendor/github.com/go-critic/go-critic/checkers/equalFold_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
"github.com/go-toolsmith/astequal"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "equalFold"
|
||||
info.Tags = []string{"performance", "experimental"}
|
||||
info.Summary = "Detects unoptimal strings/bytes case-insensitive comparison"
|
||||
info.Before = `strings.ToLower(x) == strings.ToLower(y)`
|
||||
info.After = `strings.EqualFold(x, y)`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForExpr(&equalFoldChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type equalFoldChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *equalFoldChecker) VisitExpr(e ast.Expr) {
|
||||
switch e := e.(type) {
|
||||
case *ast.CallExpr:
|
||||
c.checkBytes(e)
|
||||
case *ast.BinaryExpr:
|
||||
c.checkStrings(e)
|
||||
}
|
||||
}
|
||||
|
||||
// uncaseCall simplifies lower(x) or upper(x) to x.
|
||||
// If no simplification is applied, second return value is false.
|
||||
func (c *equalFoldChecker) uncaseCall(x ast.Expr, lower, upper string) (ast.Expr, bool) {
|
||||
call := astcast.ToCallExpr(x)
|
||||
name := qualifiedName(call.Fun)
|
||||
if name != lower && name != upper {
|
||||
return x, false
|
||||
}
|
||||
return call.Args[0], true
|
||||
}
|
||||
|
||||
func (c *equalFoldChecker) checkBytes(expr *ast.CallExpr) {
|
||||
if qualifiedName(expr.Fun) != "bytes.Equal" {
|
||||
return
|
||||
}
|
||||
|
||||
x, ok1 := c.uncaseCall(expr.Args[0], "bytes.ToLower", "bytes.ToUpper")
|
||||
y, ok2 := c.uncaseCall(expr.Args[1], "bytes.ToLower", "bytes.ToUpper")
|
||||
if !ok1 && !ok2 {
|
||||
return
|
||||
}
|
||||
if !astequal.Expr(x, y) {
|
||||
c.warnBytes(expr, x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *equalFoldChecker) checkStrings(expr *ast.BinaryExpr) {
|
||||
if expr.Op != token.EQL && expr.Op != token.NEQ {
|
||||
return
|
||||
}
|
||||
|
||||
x, ok1 := c.uncaseCall(expr.X, "strings.ToLower", "strings.ToUpper")
|
||||
y, ok2 := c.uncaseCall(expr.Y, "strings.ToLower", "strings.ToUpper")
|
||||
if !ok1 && !ok2 {
|
||||
return
|
||||
}
|
||||
if !astequal.Expr(x, y) {
|
||||
c.warnStrings(expr, x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *equalFoldChecker) warnStrings(cause ast.Node, x, y ast.Expr) {
|
||||
c.ctx.Warn(cause, "consider replacing with strings.EqualFold(%s, %s)", x, y)
|
||||
}
|
||||
|
||||
func (c *equalFoldChecker) warnBytes(cause ast.Node, x, y ast.Expr) {
|
||||
c.ctx.Warn(cause, "consider replacing with bytes.EqualFold(%s, %s)", x, y)
|
||||
}
|
87
tests/tools/vendor/github.com/go-critic/go-critic/checkers/evalOrder_checker.go
generated
vendored
Normal file
87
tests/tools/vendor/github.com/go-critic/go-critic/checkers/evalOrder_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"github.com/go-critic/go-critic/checkers/internal/lintutil"
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
"github.com/go-toolsmith/astequal"
|
||||
"github.com/go-toolsmith/typep"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "evalOrder"
|
||||
info.Tags = []string{"diagnostic", "experimental"}
|
||||
info.Summary = "Detects unwanted dependencies on the evaluation order"
|
||||
info.Before = `return x, f(&x)`
|
||||
info.After = `
|
||||
err := f(&x)
|
||||
return x, err
|
||||
`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForStmt(&evalOrderChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type evalOrderChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *evalOrderChecker) VisitStmt(stmt ast.Stmt) {
|
||||
ret := astcast.ToReturnStmt(stmt)
|
||||
if len(ret.Results) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(quasilyte): handle selector expressions like o.val in addition
|
||||
// to bare identifiers.
|
||||
addrTake := &ast.UnaryExpr{Op: token.AND}
|
||||
for _, res := range ret.Results {
|
||||
id, ok := res.(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
addrTake.X = id // addrTake is &id now
|
||||
for _, res := range ret.Results {
|
||||
call, ok := res.(*ast.CallExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// 1. Check if there is a call in form of id.method() where
|
||||
// method takes id by a pointer.
|
||||
if sel, ok := call.Fun.(*ast.SelectorExpr); ok {
|
||||
if astequal.Node(sel.X, id) && c.hasPtrRecv(sel.Sel) {
|
||||
c.warn(call)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check that there is no call that uses &id as an argument.
|
||||
dependency := lintutil.ContainsNode(call, func(n ast.Node) bool {
|
||||
return astequal.Node(addrTake, n)
|
||||
})
|
||||
if dependency {
|
||||
c.warn(call)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *evalOrderChecker) hasPtrRecv(fn *ast.Ident) bool {
|
||||
sig, ok := c.ctx.TypesInfo.TypeOf(fn).(*types.Signature)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return typep.IsPointer(sig.Recv().Type())
|
||||
}
|
||||
|
||||
func (c *evalOrderChecker) warn(call *ast.CallExpr) {
|
||||
c.ctx.Warn(call, "may want to evaluate %s before the return statement", call)
|
||||
}
|
78
tests/tools/vendor/github.com/go-critic/go-critic/checkers/exitAfterDefer_checker.go
generated
vendored
Normal file
78
tests/tools/vendor/github.com/go-critic/go-critic/checkers/exitAfterDefer_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,78 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astfmt"
|
||||
"github.com/go-toolsmith/astp"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "exitAfterDefer"
|
||||
info.Tags = []string{"diagnostic", "experimental"}
|
||||
info.Summary = "Detects calls to exit/fatal inside functions that use defer"
|
||||
info.Before = `
|
||||
defer os.Remove(filename)
|
||||
if bad {
|
||||
log.Fatalf("something bad happened")
|
||||
}`
|
||||
info.After = `
|
||||
defer os.Remove(filename)
|
||||
if bad {
|
||||
log.Printf("something bad happened")
|
||||
return
|
||||
}`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForFuncDecl(&exitAfterDeferChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type exitAfterDeferChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *exitAfterDeferChecker) VisitFuncDecl(fn *ast.FuncDecl) {
|
||||
// TODO(Quasilyte): handle goto and other kinds of flow that break
|
||||
// the algorithm below that expects the latter statement to be
|
||||
// executed after the ones that come before it.
|
||||
|
||||
var deferStmt *ast.DeferStmt
|
||||
pre := func(cur *astutil.Cursor) bool {
|
||||
// Don't recurse into local anonymous functions.
|
||||
return !astp.IsFuncLit(cur.Node())
|
||||
}
|
||||
post := func(cur *astutil.Cursor) bool {
|
||||
switch n := cur.Node().(type) {
|
||||
case *ast.DeferStmt:
|
||||
deferStmt = n
|
||||
case *ast.CallExpr:
|
||||
if deferStmt != nil {
|
||||
switch qualifiedName(n.Fun) {
|
||||
case "log.Fatal", "log.Fatalf", "log.Fatalln", "os.Exit":
|
||||
c.warn(n, deferStmt)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
astutil.Apply(fn.Body, pre, post)
|
||||
}
|
||||
|
||||
func (c *exitAfterDeferChecker) warn(cause *ast.CallExpr, deferStmt *ast.DeferStmt) {
|
||||
var s string
|
||||
if fnlit, ok := deferStmt.Call.Fun.(*ast.FuncLit); ok {
|
||||
// To avoid long and multi-line warning messages,
|
||||
// collapse the function literals.
|
||||
s = "defer " + astfmt.Sprint(fnlit.Type) + "{...}(...)"
|
||||
} else {
|
||||
s = astfmt.Sprint(deferStmt)
|
||||
}
|
||||
c.ctx.Warn(cause, "%s clutters `%s`", cause.Fun, s)
|
||||
}
|
65
tests/tools/vendor/github.com/go-critic/go-critic/checkers/flagDeref_checker.go
generated
vendored
Normal file
65
tests/tools/vendor/github.com/go-critic/go-critic/checkers/flagDeref_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "flagDeref"
|
||||
info.Tags = []string{"diagnostic"}
|
||||
info.Summary = "Detects immediate dereferencing of `flag` package pointers"
|
||||
info.Details = "Suggests to use pointer to array to avoid the copy using `&` on range expression."
|
||||
info.Before = `b := *flag.Bool("b", false, "b docs")`
|
||||
info.After = `
|
||||
var b bool
|
||||
flag.BoolVar(&b, "b", false, "b docs")`
|
||||
info.Note = `
|
||||
Dereferencing returned pointers will lead to hard to find errors
|
||||
where flag values are not updated after flag.Parse().`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
c := &flagDerefChecker{
|
||||
ctx: ctx,
|
||||
flagPtrFuncs: map[string]bool{
|
||||
"flag.Bool": true,
|
||||
"flag.Duration": true,
|
||||
"flag.Float64": true,
|
||||
"flag.Int": true,
|
||||
"flag.Int64": true,
|
||||
"flag.String": true,
|
||||
"flag.Uint": true,
|
||||
"flag.Uint64": true,
|
||||
},
|
||||
}
|
||||
return astwalk.WalkerForExpr(c)
|
||||
})
|
||||
}
|
||||
|
||||
type flagDerefChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
flagPtrFuncs map[string]bool
|
||||
}
|
||||
|
||||
func (c *flagDerefChecker) VisitExpr(expr ast.Expr) {
|
||||
if expr, ok := expr.(*ast.StarExpr); ok {
|
||||
call, ok := expr.X.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
called := qualifiedName(call.Fun)
|
||||
if c.flagPtrFuncs[called] {
|
||||
c.warn(expr, called+"Var")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *flagDerefChecker) warn(x ast.Node, suggestion string) {
|
||||
c.ctx.Warn(x, "immediate deref in %s is most likely an error; consider using %s",
|
||||
x, suggestion)
|
||||
}
|
68
tests/tools/vendor/github.com/go-critic/go-critic/checkers/flagName_checker.go
generated
vendored
Normal file
68
tests/tools/vendor/github.com/go-critic/go-critic/checkers/flagName_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "flagName"
|
||||
info.Tags = []string{"diagnostic", "experimental"}
|
||||
info.Summary = "Detects flag names with whitespace"
|
||||
info.Before = `b := flag.Bool(" foo ", false, "description")`
|
||||
info.After = `b := flag.Bool("foo", false, "description")`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForExpr(&flagNameChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type flagNameChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *flagNameChecker) VisitExpr(expr ast.Expr) {
|
||||
call := astcast.ToCallExpr(expr)
|
||||
calledExpr := astcast.ToSelectorExpr(call.Fun)
|
||||
obj, ok := c.ctx.TypesInfo.ObjectOf(astcast.ToIdent(calledExpr.X)).(*types.PkgName)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
sym := calledExpr.Sel
|
||||
pkg := obj.Imported()
|
||||
if pkg.Path() != "flag" {
|
||||
return
|
||||
}
|
||||
|
||||
switch sym.Name {
|
||||
case "Bool", "Duration", "Float64", "String",
|
||||
"Int", "Int64", "Uint", "Uint64":
|
||||
c.checkFlagName(call, call.Args[0])
|
||||
case "BoolVar", "DurationVar", "Float64Var", "StringVar",
|
||||
"IntVar", "Int64Var", "UintVar", "Uint64Var":
|
||||
c.checkFlagName(call, call.Args[1])
|
||||
}
|
||||
}
|
||||
|
||||
func (c *flagNameChecker) checkFlagName(call *ast.CallExpr, arg ast.Expr) {
|
||||
cv := c.ctx.TypesInfo.Types[arg].Value
|
||||
if cv == nil {
|
||||
return // Non-constant name
|
||||
}
|
||||
name := constant.StringVal(cv)
|
||||
if strings.Contains(name, " ") {
|
||||
c.warnWhitespace(call, name)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *flagNameChecker) warnWhitespace(cause ast.Node, name string) {
|
||||
c.ctx.Warn(cause, "flag name %q contains whitespace", name)
|
||||
}
|
60
tests/tools/vendor/github.com/go-critic/go-critic/checkers/hexLiteral_checker.go
generated
vendored
Normal file
60
tests/tools/vendor/github.com/go-critic/go-critic/checkers/hexLiteral_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "hexLiteral"
|
||||
info.Tags = []string{"style", "experimental"}
|
||||
info.Summary = "Detects hex literals that have mixed case letter digits"
|
||||
info.Before = `
|
||||
x := 0X12
|
||||
y := 0xfF`
|
||||
info.After = `
|
||||
x := 0x12
|
||||
// (A)
|
||||
y := 0xff
|
||||
// (B)
|
||||
y := 0xFF`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForExpr(&hexLiteralChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type hexLiteralChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *hexLiteralChecker) warn0X(lit *ast.BasicLit) {
|
||||
suggest := "0x" + lit.Value[len("0X"):]
|
||||
c.ctx.Warn(lit, "prefer 0x over 0X, s/%s/%s/", lit.Value, suggest)
|
||||
}
|
||||
|
||||
func (c *hexLiteralChecker) warnMixedDigits(lit *ast.BasicLit) {
|
||||
c.ctx.Warn(lit, "don't mix hex literal letter digits casing")
|
||||
}
|
||||
|
||||
func (c *hexLiteralChecker) VisitExpr(expr ast.Expr) {
|
||||
lit := astcast.ToBasicLit(expr)
|
||||
if lit.Kind != token.INT || len(lit.Value) < 3 {
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(lit.Value, "0X") {
|
||||
c.warn0X(lit)
|
||||
return
|
||||
}
|
||||
digits := lit.Value[len("0x"):]
|
||||
if strings.ToLower(digits) != digits && strings.ToUpper(digits) != digits {
|
||||
c.warnMixedDigits(lit)
|
||||
}
|
||||
}
|
63
tests/tools/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go
generated
vendored
Normal file
63
tests/tools/vendor/github.com/go-critic/go-critic/checkers/hugeParam_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "hugeParam"
|
||||
info.Tags = []string{"performance"}
|
||||
info.Params = lintpack.CheckerParams{
|
||||
"sizeThreshold": {
|
||||
Value: 80,
|
||||
Usage: "size in bytes that makes the warning trigger",
|
||||
},
|
||||
}
|
||||
info.Summary = "Detects params that incur excessive amount of copying"
|
||||
info.Before = `func f(x [1024]int) {}`
|
||||
info.After = `func f(x *[1024]int) {}`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForFuncDecl(&hugeParamChecker{
|
||||
ctx: ctx,
|
||||
sizeThreshold: int64(info.Params.Int("sizeThreshold")),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type hugeParamChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
sizeThreshold int64
|
||||
}
|
||||
|
||||
func (c *hugeParamChecker) VisitFuncDecl(decl *ast.FuncDecl) {
|
||||
// TODO(quasilyte): maybe it's worthwhile to permit skipping
|
||||
// test files for this checker?
|
||||
if decl.Recv != nil {
|
||||
c.checkParams(decl.Recv.List)
|
||||
}
|
||||
c.checkParams(decl.Type.Params.List)
|
||||
}
|
||||
|
||||
func (c *hugeParamChecker) checkParams(params []*ast.Field) {
|
||||
for _, p := range params {
|
||||
for _, id := range p.Names {
|
||||
typ := c.ctx.TypesInfo.TypeOf(id)
|
||||
size := c.ctx.SizesInfo.Sizeof(typ)
|
||||
if size >= c.sizeThreshold {
|
||||
c.warn(id, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *hugeParamChecker) warn(cause *ast.Ident, size int64) {
|
||||
c.ctx.Warn(cause, "%s is heavy (%d bytes); consider passing it by pointer",
|
||||
cause, size)
|
||||
}
|
99
tests/tools/vendor/github.com/go-critic/go-critic/checkers/ifElseChain_checker.go
generated
vendored
Normal file
99
tests/tools/vendor/github.com/go-critic/go-critic/checkers/ifElseChain_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "ifElseChain"
|
||||
info.Tags = []string{"style"}
|
||||
info.Summary = "Detects repeated if-else statements and suggests to replace them with switch statement"
|
||||
info.Before = `
|
||||
if cond1 {
|
||||
// Code A.
|
||||
} else if cond2 {
|
||||
// Code B.
|
||||
} else {
|
||||
// Code C.
|
||||
}`
|
||||
info.After = `
|
||||
switch {
|
||||
case cond1:
|
||||
// Code A.
|
||||
case cond2:
|
||||
// Code B.
|
||||
default:
|
||||
// Code C.
|
||||
}`
|
||||
info.Note = `
|
||||
Permits single else or else-if; repeated else-if or else + else-if
|
||||
will trigger suggestion to use switch statement.
|
||||
See [EffectiveGo#switch](https://golang.org/doc/effective_go.html#switch).`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForStmt(&ifElseChainChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type ifElseChainChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
cause *ast.IfStmt
|
||||
visited map[*ast.IfStmt]bool
|
||||
}
|
||||
|
||||
func (c *ifElseChainChecker) EnterFunc(fn *ast.FuncDecl) bool {
|
||||
if fn.Body == nil {
|
||||
return false
|
||||
}
|
||||
c.visited = make(map[*ast.IfStmt]bool)
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *ifElseChainChecker) VisitStmt(stmt ast.Stmt) {
|
||||
if stmt, ok := stmt.(*ast.IfStmt); ok {
|
||||
if c.visited[stmt] {
|
||||
return
|
||||
}
|
||||
c.cause = stmt
|
||||
c.checkIfStmt(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ifElseChainChecker) checkIfStmt(stmt *ast.IfStmt) {
|
||||
const minThreshold = 2
|
||||
if c.countIfelseLen(stmt) >= minThreshold {
|
||||
c.warn()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ifElseChainChecker) countIfelseLen(stmt *ast.IfStmt) int {
|
||||
count := 0
|
||||
for {
|
||||
switch e := stmt.Else.(type) {
|
||||
case *ast.IfStmt:
|
||||
if e.Init != nil {
|
||||
return 0 // Give up
|
||||
}
|
||||
// Else if.
|
||||
stmt = e
|
||||
count++
|
||||
c.visited[e] = true
|
||||
case *ast.BlockStmt:
|
||||
// Else branch.
|
||||
return count + 1
|
||||
default:
|
||||
// No else or else if.
|
||||
return count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ifElseChainChecker) warn() {
|
||||
c.ctx.Warn(c.cause, "rewrite if-else to switch statement")
|
||||
}
|
47
tests/tools/vendor/github.com/go-critic/go-critic/checkers/importShadow_checker.go
generated
vendored
Normal file
47
tests/tools/vendor/github.com/go-critic/go-critic/checkers/importShadow_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "importShadow"
|
||||
info.Tags = []string{"style", "opinionated"}
|
||||
info.Summary = "Detects when imported package names shadowed in the assignments"
|
||||
info.Before = `
|
||||
// "path/filepath" is imported.
|
||||
filepath := "foo.txt"`
|
||||
info.After = `
|
||||
filename := "foo.txt"`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
ctx.Require.PkgObjects = true
|
||||
return astwalk.WalkerForLocalDef(&importShadowChecker{ctx: ctx}, ctx.TypesInfo)
|
||||
})
|
||||
}
|
||||
|
||||
type importShadowChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *importShadowChecker) VisitLocalDef(def astwalk.Name, _ ast.Expr) {
|
||||
for pkgObj, name := range c.ctx.PkgObjects {
|
||||
if name == def.ID.Name && name != "_" {
|
||||
c.warn(def.ID, name, pkgObj.Imported())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *importShadowChecker) warn(id ast.Node, importedName string, pkg *types.Package) {
|
||||
if isStdlibPkg(pkg) {
|
||||
c.ctx.Warn(id, "shadow of imported package '%s'", importedName)
|
||||
} else {
|
||||
c.ctx.Warn(id, "shadow of imported from '%s' package '%s'", pkg.Path(), importedName)
|
||||
}
|
||||
}
|
50
tests/tools/vendor/github.com/go-critic/go-critic/checkers/indexAlloc_checker.go
generated
vendored
Normal file
50
tests/tools/vendor/github.com/go-critic/go-critic/checkers/indexAlloc_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
"github.com/go-toolsmith/typep"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "indexAlloc"
|
||||
info.Tags = []string{"performance"}
|
||||
info.Summary = "Detects strings.Index calls that may cause unwanted allocs"
|
||||
info.Before = `strings.Index(string(x), y)`
|
||||
info.After = `bytes.Index(x, []byte(y))`
|
||||
info.Note = `See Go issue for details: https://github.com/golang/go/issues/25864`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForExpr(&indexAllocChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type indexAllocChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *indexAllocChecker) VisitExpr(e ast.Expr) {
|
||||
call := astcast.ToCallExpr(e)
|
||||
if qualifiedName(call.Fun) != "strings.Index" {
|
||||
return
|
||||
}
|
||||
stringConv := astcast.ToCallExpr(call.Args[0])
|
||||
if qualifiedName(stringConv.Fun) != "string" {
|
||||
return
|
||||
}
|
||||
x := stringConv.Args[0]
|
||||
y := call.Args[1]
|
||||
if typep.SideEffectFree(c.ctx.TypesInfo, x) && typep.SideEffectFree(c.ctx.TypesInfo, y) {
|
||||
c.warn(e, x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *indexAllocChecker) warn(cause ast.Node, x, y ast.Expr) {
|
||||
c.ctx.Warn(cause, "consider replacing %s with bytes.Index(%s, []byte(%s))",
|
||||
cause, x, y)
|
||||
}
|
56
tests/tools/vendor/github.com/go-critic/go-critic/checkers/initClause_checker.go
generated
vendored
Normal file
56
tests/tools/vendor/github.com/go-critic/go-critic/checkers/initClause_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "initClause"
|
||||
info.Tags = []string{"style", "opinionated", "experimental"}
|
||||
info.Summary = "Detects non-assignment statements inside if/switch init clause"
|
||||
info.Before = `if sideEffect(); cond {
|
||||
}`
|
||||
info.After = `sideEffect()
|
||||
if cond {
|
||||
}`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForStmt(&initClauseChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type initClauseChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *initClauseChecker) VisitStmt(stmt ast.Stmt) {
|
||||
initClause := c.getInitClause(stmt)
|
||||
if initClause != nil && !astp.IsAssignStmt(initClause) {
|
||||
c.warn(stmt, initClause)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *initClauseChecker) getInitClause(x ast.Stmt) ast.Stmt {
|
||||
switch x := x.(type) {
|
||||
case *ast.IfStmt:
|
||||
return x.Init
|
||||
case *ast.SwitchStmt:
|
||||
return x.Init
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *initClauseChecker) warn(stmt, clause ast.Stmt) {
|
||||
name := "if"
|
||||
if astp.IsSwitchStmt(stmt) {
|
||||
name = "switch"
|
||||
}
|
||||
c.ctx.Warn(stmt, "consider to move `%s` before %s", clause, name)
|
||||
}
|
27
tests/tools/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astfind.go
generated
vendored
Normal file
27
tests/tools/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astfind.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
package lintutil
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
// FindNode applies pred for root and all it's childs until it returns true.
|
||||
// Matched node is returned.
|
||||
// If none of the nodes matched predicate, nil is returned.
|
||||
func FindNode(root ast.Node, pred func(ast.Node) bool) ast.Node {
|
||||
var found ast.Node
|
||||
astutil.Apply(root, nil, func(cur *astutil.Cursor) bool {
|
||||
if pred(cur.Node()) {
|
||||
found = cur.Node()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
// ContainsNode reports whether `FindNode(root, pred)!=nil`.
|
||||
func ContainsNode(root ast.Node, pred func(ast.Node) bool) bool {
|
||||
return FindNode(root, pred) != nil
|
||||
}
|
86
tests/tools/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astflow.go
generated
vendored
Normal file
86
tests/tools/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astflow.go
generated
vendored
Normal file
|
@ -0,0 +1,86 @@
|
|||
package lintutil
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"github.com/go-toolsmith/astequal"
|
||||
"github.com/go-toolsmith/astp"
|
||||
"github.com/go-toolsmith/typep"
|
||||
)
|
||||
|
||||
// Different utilities to make simple analysis over typed ast values flow.
|
||||
//
|
||||
// It's primitive and can't replace SSA, but the bright side is that
|
||||
// it does not require building an additional IR eagerly.
|
||||
// Expected to be used sparingly inside a few checkers.
|
||||
//
|
||||
// If proven really useful, can be moved to go-toolsmith library.
|
||||
|
||||
// IsImmutable reports whether n can be midified through any operation.
|
||||
func IsImmutable(info *types.Info, n ast.Expr) bool {
|
||||
if astp.IsBasicLit(n) {
|
||||
return true
|
||||
}
|
||||
tv, ok := info.Types[n]
|
||||
return ok && !tv.Assignable() && !tv.Addressable()
|
||||
}
|
||||
|
||||
// CouldBeMutated reports whether dst can be modified inside body.
|
||||
//
|
||||
// Note that it does not take already existing pointers to dst.
|
||||
// An example of safe and correct usage is checking of something
|
||||
// that was just defined, so the dst is a result of that definition.
|
||||
func CouldBeMutated(info *types.Info, body ast.Node, dst ast.Expr) bool {
|
||||
if IsImmutable(info, dst) { // Fast path.
|
||||
return false
|
||||
}
|
||||
|
||||
// We don't track pass-by-value.
|
||||
// If it's already a pointer, passing it by value
|
||||
// means that there can be a potential indirect modification.
|
||||
//
|
||||
// It's possible to be less conservative here and find at least
|
||||
// one such value pass before giving up.
|
||||
if typep.IsPointer(info.TypeOf(dst)) {
|
||||
return true
|
||||
}
|
||||
|
||||
var isDst func(x ast.Expr) bool
|
||||
if dst, ok := dst.(*ast.Ident); ok {
|
||||
// Identifier can be shadowed,
|
||||
// so we need to check the object as well.
|
||||
obj := info.ObjectOf(dst)
|
||||
if obj == nil {
|
||||
return true // Being conservative
|
||||
}
|
||||
isDst = func(x ast.Expr) bool {
|
||||
id, ok := x.(*ast.Ident)
|
||||
return ok && id.Name == dst.Name && info.ObjectOf(id) == obj
|
||||
}
|
||||
} else {
|
||||
isDst = func(x ast.Expr) bool {
|
||||
return astequal.Expr(dst, x)
|
||||
}
|
||||
}
|
||||
|
||||
return ContainsNode(body, func(n ast.Node) bool {
|
||||
switch n := n.(type) {
|
||||
case *ast.UnaryExpr:
|
||||
if n.Op == token.AND && isDst(n.X) {
|
||||
return true // Address taken
|
||||
}
|
||||
case *ast.AssignStmt:
|
||||
for _, lhs := range n.Lhs {
|
||||
if isDst(lhs) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case *ast.IncDecStmt:
|
||||
// Incremented or decremented.
|
||||
return isDst(n.X)
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
44
tests/tools/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astset.go
generated
vendored
Normal file
44
tests/tools/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/astset.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
package lintutil
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-toolsmith/astequal"
|
||||
)
|
||||
|
||||
// AstSet is a simple ast.Node set.
|
||||
// Zero value is ready to use set.
|
||||
// Can be reused after Clear call.
|
||||
type AstSet struct {
|
||||
items []ast.Node
|
||||
}
|
||||
|
||||
// Contains reports whether s contains x.
|
||||
func (s *AstSet) Contains(x ast.Node) bool {
|
||||
for i := range s.items {
|
||||
if astequal.Node(s.items[i], x) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Insert pushes x in s if it's not already there.
|
||||
// Returns true if element was inserted.
|
||||
func (s *AstSet) Insert(x ast.Node) bool {
|
||||
if s.Contains(x) {
|
||||
return false
|
||||
}
|
||||
s.items = append(s.items, x)
|
||||
return true
|
||||
}
|
||||
|
||||
// Clear removes all element from set.
|
||||
func (s *AstSet) Clear() {
|
||||
s.items = s.items[:0]
|
||||
}
|
||||
|
||||
// Len returns the number of elements contained inside s.
|
||||
func (s *AstSet) Len() int {
|
||||
return len(s.items)
|
||||
}
|
37
tests/tools/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/lintutil.go
generated
vendored
Normal file
37
tests/tools/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/lintutil.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
package lintutil
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// TODO: this package is a way to reuse code between lint and astwalk.
|
||||
// Would be good to find it a better name.
|
||||
|
||||
// IsTypeExpr reports whether x represents type expression.
|
||||
//
|
||||
// Type expression does not evaluate to any run time value,
|
||||
// but rather describes type that is used inside Go expression.
|
||||
// For example, (*T)(v) is a CallExpr that "calls" (*T).
|
||||
// (*T) is a type expression that tells Go compiler type v should be converted to.
|
||||
func IsTypeExpr(info *types.Info, x ast.Expr) bool {
|
||||
switch x := x.(type) {
|
||||
case *ast.StarExpr:
|
||||
return IsTypeExpr(info, x.X)
|
||||
case *ast.ParenExpr:
|
||||
return IsTypeExpr(info, x.X)
|
||||
case *ast.SelectorExpr:
|
||||
return IsTypeExpr(info, x.Sel)
|
||||
case *ast.Ident:
|
||||
// Identifier may be a type expression if object
|
||||
// it reffers to is a type name.
|
||||
_, ok := info.ObjectOf(x).(*types.TypeName)
|
||||
return ok
|
||||
|
||||
case *ast.FuncType, *ast.StructType, *ast.InterfaceType, *ast.ArrayType, *ast.MapType:
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
94
tests/tools/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/zero_value.go
generated
vendored
Normal file
94
tests/tools/vendor/github.com/go-critic/go-critic/checkers/internal/lintutil/zero_value.go
generated
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
package lintutil
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// IsZeroValue reports whether x represents zero value of its type.
|
||||
//
|
||||
// The functions is conservative and may return false for zero values
|
||||
// if some cases are not handled in a comprehensive way
|
||||
// but is should never return true for something that's not a proper zv.
|
||||
func IsZeroValue(info *types.Info, x ast.Expr) bool {
|
||||
switch x := x.(type) {
|
||||
case *ast.BasicLit:
|
||||
typ := info.TypeOf(x).Underlying().(*types.Basic)
|
||||
v := info.Types[x].Value
|
||||
var z constant.Value
|
||||
switch {
|
||||
case typ.Kind() == types.String:
|
||||
z = constant.MakeString("")
|
||||
case typ.Info()&types.IsInteger != 0:
|
||||
z = constant.MakeInt64(0)
|
||||
case typ.Info()&types.IsUnsigned != 0:
|
||||
z = constant.MakeUint64(0)
|
||||
case typ.Info()&types.IsFloat != 0:
|
||||
z = constant.MakeFloat64(0)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return constant.Compare(v, token.EQL, z)
|
||||
|
||||
case *ast.CompositeLit:
|
||||
return len(x.Elts) == 0
|
||||
|
||||
default:
|
||||
// Note that this function is not comprehensive.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ZeroValueOf returns a zero value expression for typeExpr of type typ.
|
||||
// If function can't find such a value, nil is returned.
|
||||
func ZeroValueOf(typeExpr ast.Expr, typ types.Type) ast.Expr {
|
||||
switch utyp := typ.Underlying().(type) {
|
||||
case *types.Basic:
|
||||
info := utyp.Info()
|
||||
var zv ast.Expr
|
||||
switch {
|
||||
case info&types.IsInteger != 0:
|
||||
zv = &ast.BasicLit{Kind: token.INT, Value: "0"}
|
||||
case info&types.IsFloat != 0:
|
||||
zv = &ast.BasicLit{Kind: token.FLOAT, Value: "0.0"}
|
||||
case info&types.IsString != 0:
|
||||
zv = &ast.BasicLit{Kind: token.STRING, Value: `""`}
|
||||
case info&types.IsBoolean != 0:
|
||||
zv = &ast.Ident{Name: "false"}
|
||||
}
|
||||
if isDefaultLiteralType(typ) {
|
||||
return zv
|
||||
}
|
||||
return &ast.CallExpr{
|
||||
Fun: typeExpr,
|
||||
Args: []ast.Expr{zv},
|
||||
}
|
||||
|
||||
case *types.Slice, *types.Map, *types.Pointer, *types.Interface:
|
||||
return &ast.CallExpr{
|
||||
Fun: typeExpr,
|
||||
Args: []ast.Expr{&ast.Ident{Name: "nil"}},
|
||||
}
|
||||
|
||||
case *types.Array, *types.Struct:
|
||||
return &ast.CompositeLit{Type: typeExpr}
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func isDefaultLiteralType(typ types.Type) bool {
|
||||
btyp, ok := typ.(*types.Basic)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
switch btyp.Kind() {
|
||||
case types.Bool, types.Int, types.Float64, types.String:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
57
tests/tools/vendor/github.com/go-critic/go-critic/checkers/methodExprCall_checker.go
generated
vendored
Normal file
57
tests/tools/vendor/github.com/go-critic/go-critic/checkers/methodExprCall_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
"github.com/go-toolsmith/astcopy"
|
||||
"github.com/go-toolsmith/typep"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "methodExprCall"
|
||||
info.Tags = []string{"style", "experimental"}
|
||||
info.Summary = "Detects method expression call that can be replaced with a method call"
|
||||
info.Before = `f := foo{}
|
||||
foo.bar(f)`
|
||||
info.After = `f := foo{}
|
||||
f.bar()`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForExpr(&methodExprCallChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type methodExprCallChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *methodExprCallChecker) VisitExpr(x ast.Expr) {
|
||||
call := astcast.ToCallExpr(x)
|
||||
s := astcast.ToSelectorExpr(call.Fun)
|
||||
|
||||
if len(call.Args) < 1 || astcast.ToIdent(call.Args[0]).Name == "nil" {
|
||||
return
|
||||
}
|
||||
|
||||
if typep.IsTypeExpr(c.ctx.TypesInfo, s.X) {
|
||||
c.warn(call, s)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *methodExprCallChecker) warn(cause *ast.CallExpr, s *ast.SelectorExpr) {
|
||||
selector := astcopy.SelectorExpr(s)
|
||||
selector.X = cause.Args[0]
|
||||
|
||||
// Remove "&" from the receiver (if any).
|
||||
if u, ok := selector.X.(*ast.UnaryExpr); ok && u.Op == token.AND {
|
||||
selector.X = u.X
|
||||
}
|
||||
|
||||
c.ctx.Warn(cause, "consider to change `%s` to `%s`", cause.Fun, selector)
|
||||
}
|
73
tests/tools/vendor/github.com/go-critic/go-critic/checkers/nestingReduce_checker.go
generated
vendored
Normal file
73
tests/tools/vendor/github.com/go-critic/go-critic/checkers/nestingReduce_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "nestingReduce"
|
||||
info.Tags = []string{"style", "opinionated", "experimental"}
|
||||
info.Params = lintpack.CheckerParams{
|
||||
"bodyWidth": {
|
||||
Value: 5,
|
||||
Usage: "min number of statements inside a branch to trigger a warning",
|
||||
},
|
||||
}
|
||||
info.Summary = "Finds where nesting level could be reduced"
|
||||
info.Before = `
|
||||
for _, v := range a {
|
||||
if v.Bool {
|
||||
body()
|
||||
}
|
||||
}`
|
||||
info.After = `
|
||||
for _, v := range a {
|
||||
if !v.Bool {
|
||||
continue
|
||||
}
|
||||
body()
|
||||
}`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
c := &nestingReduceChecker{ctx: ctx}
|
||||
c.bodyWidth = info.Params.Int("bodyWidth")
|
||||
return astwalk.WalkerForStmt(c)
|
||||
})
|
||||
}
|
||||
|
||||
type nestingReduceChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
bodyWidth int
|
||||
}
|
||||
|
||||
func (c *nestingReduceChecker) VisitStmt(stmt ast.Stmt) {
|
||||
switch stmt := stmt.(type) {
|
||||
case *ast.ForStmt:
|
||||
c.checkLoopBody(stmt.Body.List)
|
||||
case *ast.RangeStmt:
|
||||
c.checkLoopBody(stmt.Body.List)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nestingReduceChecker) checkLoopBody(body []ast.Stmt) {
|
||||
if len(body) != 1 {
|
||||
return
|
||||
}
|
||||
stmt, ok := body[0].(*ast.IfStmt)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if len(stmt.Body.List) >= c.bodyWidth && stmt.Else == nil {
|
||||
c.warnLoop(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nestingReduceChecker) warnLoop(cause ast.Node) {
|
||||
c.ctx.Warn(cause, "invert if cond, replace body with `continue`, move old body after the statement")
|
||||
}
|
45
tests/tools/vendor/github.com/go-critic/go-critic/checkers/newDeref_checker.go
generated
vendored
Normal file
45
tests/tools/vendor/github.com/go-critic/go-critic/checkers/newDeref_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/go-critic/go-critic/checkers/internal/lintutil"
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "newDeref"
|
||||
info.Tags = []string{"style", "experimental"}
|
||||
info.Summary = "Detects immediate dereferencing of `new` expressions"
|
||||
info.Before = `x := *new(bool)`
|
||||
info.After = `x := false`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForExpr(&newDerefChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type newDerefChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *newDerefChecker) VisitExpr(expr ast.Expr) {
|
||||
deref := astcast.ToStarExpr(expr)
|
||||
call := astcast.ToCallExpr(deref.X)
|
||||
if astcast.ToIdent(call.Fun).Name == "new" {
|
||||
typ := c.ctx.TypesInfo.TypeOf(call.Args[0])
|
||||
zv := lintutil.ZeroValueOf(astutil.Unparen(call.Args[0]), typ)
|
||||
if zv != nil {
|
||||
c.warn(expr, zv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *newDerefChecker) warn(cause, suggestion ast.Expr) {
|
||||
c.ctx.Warn(cause, "replace `%s` with `%s`", cause, suggestion)
|
||||
}
|
64
tests/tools/vendor/github.com/go-critic/go-critic/checkers/nilValReturn_checker.go
generated
vendored
Normal file
64
tests/tools/vendor/github.com/go-critic/go-critic/checkers/nilValReturn_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astequal"
|
||||
"github.com/go-toolsmith/typep"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "nilValReturn"
|
||||
info.Tags = []string{"diagnostic", "experimental"}
|
||||
info.Summary = "Detects return statements those results evaluate to nil"
|
||||
info.Before = `
|
||||
if err == nil {
|
||||
return err
|
||||
}`
|
||||
info.After = `
|
||||
// (A) - return nil explicitly
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// (B) - typo in "==", change to "!="
|
||||
if err != nil {
|
||||
return err
|
||||
}`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
return astwalk.WalkerForStmt(&nilValReturnChecker{ctx: ctx})
|
||||
})
|
||||
}
|
||||
|
||||
type nilValReturnChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
}
|
||||
|
||||
func (c *nilValReturnChecker) VisitStmt(stmt ast.Stmt) {
|
||||
ifStmt, ok := stmt.(*ast.IfStmt)
|
||||
if !ok || len(ifStmt.Body.List) != 1 {
|
||||
return
|
||||
}
|
||||
ret, ok := ifStmt.Body.List[0].(*ast.ReturnStmt)
|
||||
if !ok || len(ret.Results) != 1 {
|
||||
return
|
||||
}
|
||||
expr, ok := ifStmt.Cond.(*ast.BinaryExpr)
|
||||
cond := ok &&
|
||||
expr.Op == token.EQL &&
|
||||
typep.SideEffectFree(c.ctx.TypesInfo, expr.X) &&
|
||||
qualifiedName(expr.Y) == "nil" &&
|
||||
astequal.Expr(expr.X, ret.Results[0])
|
||||
if cond {
|
||||
c.warn(ret, expr.X)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nilValReturnChecker) warn(cause, val ast.Node) {
|
||||
c.ctx.Warn(cause, "returned expr is always nil; replace %s with nil", val)
|
||||
}
|
82
tests/tools/vendor/github.com/go-critic/go-critic/checkers/octalLiteral_checker.go
generated
vendored
Normal file
82
tests/tools/vendor/github.com/go-critic/go-critic/checkers/octalLiteral_checker.go
generated
vendored
Normal file
|
@ -0,0 +1,82 @@
|
|||
package checkers
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"github.com/go-lintpack/lintpack"
|
||||
"github.com/go-lintpack/lintpack/astwalk"
|
||||
"github.com/go-toolsmith/astcast"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var info lintpack.CheckerInfo
|
||||
info.Name = "octalLiteral"
|
||||
info.Tags = []string{"diagnostic", "experimental"}
|
||||
info.Summary = "Detects octal literals passed to functions"
|
||||
info.Before = `foo(02)`
|
||||
info.After = `foo(2)`
|
||||
|
||||
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
|
||||
c := &octalLiteralChecker{
|
||||
ctx: ctx,
|
||||
octFriendlyPkg: map[string]bool{
|
||||
"os": true,
|
||||
"io/ioutil": true,
|
||||
},
|
||||
}
|
||||
return astwalk.WalkerForExpr(c)
|
||||
})
|
||||
}
|
||||
|
||||
type octalLiteralChecker struct {
|
||||
astwalk.WalkHandler
|
||||
ctx *lintpack.CheckerContext
|
||||
|
||||
octFriendlyPkg map[string]bool
|
||||
}
|
||||
|
||||
func (c *octalLiteralChecker) VisitExpr(expr ast.Expr) {
|
||||
call := astcast.ToCallExpr(expr)
|
||||
calledExpr := astcast.ToSelectorExpr(call.Fun)
|
||||
ident := astcast.ToIdent(calledExpr.X)
|
||||
|
||||
if obj, ok := c.ctx.TypesInfo.ObjectOf(ident).(*types.PkgName); ok {
|
||||
pkg := obj.Imported()
|
||||
if c.octFriendlyPkg[pkg.Path()] {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, arg := range call.Args {
|
||||
if lit := astcast.ToBasicLit(c.unsign(arg)); len(lit.Value) > 1 &&
|
||||
c.isIntLiteral(lit) &&
|
||||
c.isOctalLiteral(lit) {
|
||||
c.warn(call)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *octalLiteralChecker) unsign(e ast.Expr) ast.Expr {
|
||||
u, ok := e.(*ast.UnaryExpr)
|
||||
if !ok {
|
||||
return e
|
||||
}
|
||||
return u.X
|
||||
}
|
||||
|
||||
func (c *octalLiteralChecker) isIntLiteral(lit *ast.BasicLit) bool {
|
||||
return lit.Kind == token.INT
|
||||
}
|
||||
|
||||
func (c *octalLiteralChecker) isOctalLiteral(lit *ast.BasicLit) bool {
|
||||
return lit.Value[0] == '0' &&
|
||||
lit.Value[1] != 'x' &&
|
||||
lit.Value[1] != 'X'
|
||||
}
|
||||
|
||||
func (c *octalLiteralChecker) warn(expr ast.Expr) {
|
||||
c.ctx.Warn(expr, "suspicious octal args in `%s`", expr)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue