tests/tools: bump golangci-lint
... and its dependencies. Also, modify .golangci.yml since new golangci-lint no longer have some of the linters mentioned. Besides, it is unsafe to enable all linters, because (1) not all linters are good/useful; (2) new golangci-lint releases can bring more linters and thus more CI issues. Instead, use the default set of linters, plus enable a few more: * revive (which replaces golint) * unconvert Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This commit is contained in:
		
							parent
							
								
									7e691dc170
								
							
						
					
					
						commit
						8c14d40c4b
					
				|  | @ -7,18 +7,6 @@ run: | ||||||
|   # Don't exceed number of threads available when running under CI |   # Don't exceed number of threads available when running under CI | ||||||
|   concurrency: 4 |   concurrency: 4 | ||||||
| linters: | linters: | ||||||
|   enable-all: true |   enable: | ||||||
|   disable: |     - revive | ||||||
|     # All these break for one reason or another |     - unconvert | ||||||
|     - dupl |  | ||||||
|     - funlen |  | ||||||
|     - gochecknoglobals |  | ||||||
|     - gochecknoinits |  | ||||||
|     - goconst |  | ||||||
|     - gocritic |  | ||||||
|     - gocyclo |  | ||||||
|     - gosec |  | ||||||
|     - lll |  | ||||||
|     - maligned |  | ||||||
|     - prealloc |  | ||||||
|     - scopelint |  | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ go 1.14 | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
| 	github.com/cpuguy83/go-md2man v1.0.10 | 	github.com/cpuguy83/go-md2man v1.0.10 | ||||||
| 	github.com/golangci/golangci-lint v1.18.0 | 	github.com/golangci/golangci-lint v1.43.0 | ||||||
| 	github.com/onsi/ginkgo v1.8.0 | 	github.com/onsi/ginkgo v1.16.4 | ||||||
| 	github.com/vbatts/git-validation v1.1.1-0.20200916181008-60cb8713913d | 	github.com/vbatts/git-validation v1.1.1-0.20200916181008-60cb8713913d | ||||||
| ) | ) | ||||||
|  |  | ||||||
							
								
								
									
										1357
									
								
								tests/tools/go.sum
								
								
								
								
							
							
						
						
									
										1357
									
								
								tests/tools/go.sum
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | MIT License | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2018 Leigh McCulloch | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
							
								
								
									
										187
									
								
								tests/tools/vendor/4d63.com/gochecknoglobals/checknoglobals/check_no_globals.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										187
									
								
								tests/tools/vendor/4d63.com/gochecknoglobals/checknoglobals/check_no_globals.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,187 @@ | ||||||
|  | package checknoglobals | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"flag" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/token" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/go/analysis" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // allowedExpression is a struct representing packages and methods that will
 | ||||||
|  | // be an allowed combination to use as a global variable, f.ex. Name `regexp`
 | ||||||
|  | // and SelName `MustCompile`.
 | ||||||
|  | type allowedExpression struct { | ||||||
|  | 	Name    string | ||||||
|  | 	SelName string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const Doc = `check that no global variables exist | ||||||
|  | 
 | ||||||
|  | This analyzer checks for global variables and errors on any found. | ||||||
|  | 
 | ||||||
|  | A global variable is a variable declared in package scope and that can be read | ||||||
|  | and written to by any function within the package. Global variables can cause | ||||||
|  | side effects which are difficult to keep track of. A code in one function may | ||||||
|  | change the variables state while another unrelated chunk of code may be | ||||||
|  | effected by it.` | ||||||
|  | 
 | ||||||
|  | // Analyzer provides an Analyzer that checks that there are no global
 | ||||||
|  | // variables, except for errors and variables containing regular
 | ||||||
|  | // expressions.
 | ||||||
|  | func Analyzer() *analysis.Analyzer { | ||||||
|  | 	return &analysis.Analyzer{ | ||||||
|  | 		Name:             "gochecknoglobals", | ||||||
|  | 		Doc:              Doc, | ||||||
|  | 		Run:              checkNoGlobals, | ||||||
|  | 		Flags:            flags(), | ||||||
|  | 		RunDespiteErrors: true, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func flags() flag.FlagSet { | ||||||
|  | 	flags := flag.NewFlagSet("", flag.ExitOnError) | ||||||
|  | 	flags.Bool("t", false, "Include tests") | ||||||
|  | 
 | ||||||
|  | 	return *flags | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isAllowed(cm ast.CommentMap, v ast.Node) bool { | ||||||
|  | 	switch i := v.(type) { | ||||||
|  | 	case *ast.GenDecl: | ||||||
|  | 		return hasEmbedComment(cm, i) | ||||||
|  | 	case *ast.Ident: | ||||||
|  | 		return i.Name == "_" || i.Name == "version" || looksLikeError(i) || identHasEmbedComment(cm, i) | ||||||
|  | 	case *ast.CallExpr: | ||||||
|  | 		if expr, ok := i.Fun.(*ast.SelectorExpr); ok { | ||||||
|  | 			return isAllowedSelectorExpression(expr) | ||||||
|  | 		} | ||||||
|  | 	case *ast.CompositeLit: | ||||||
|  | 		if expr, ok := i.Type.(*ast.SelectorExpr); ok { | ||||||
|  | 			return isAllowedSelectorExpression(expr) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isAllowedSelectorExpression(v *ast.SelectorExpr) bool { | ||||||
|  | 	x, ok := v.X.(*ast.Ident) | ||||||
|  | 	if !ok { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	allowList := []allowedExpression{ | ||||||
|  | 		{Name: "regexp", SelName: "MustCompile"}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, i := range allowList { | ||||||
|  | 		if x.Name == i.Name && v.Sel.Name == i.SelName { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // looksLikeError returns true if the AST identifier starts
 | ||||||
|  | // with 'err' or 'Err', or false otherwise.
 | ||||||
|  | //
 | ||||||
|  | // TODO: https://github.com/leighmcculloch/gochecknoglobals/issues/5
 | ||||||
|  | func looksLikeError(i *ast.Ident) bool { | ||||||
|  | 	prefix := "err" | ||||||
|  | 	if i.IsExported() { | ||||||
|  | 		prefix = "Err" | ||||||
|  | 	} | ||||||
|  | 	return strings.HasPrefix(i.Name, prefix) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func identHasEmbedComment(cm ast.CommentMap, i *ast.Ident) bool { | ||||||
|  | 	if i.Obj == nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	spec, ok := i.Obj.Decl.(*ast.ValueSpec) | ||||||
|  | 	if !ok { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return hasEmbedComment(cm, spec) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // hasEmbedComment returns true if the AST node has
 | ||||||
|  | // a '//go:embed ' comment, or false otherwise.
 | ||||||
|  | func hasEmbedComment(cm ast.CommentMap, n ast.Node) bool { | ||||||
|  | 	for _, g := range cm[n] { | ||||||
|  | 		for _, c := range g.List { | ||||||
|  | 			if strings.HasPrefix(c.Text, "//go:embed ") { | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func checkNoGlobals(pass *analysis.Pass) (interface{}, error) { | ||||||
|  | 	includeTests := pass.Analyzer.Flags.Lookup("t").Value.(flag.Getter).Get().(bool) | ||||||
|  | 
 | ||||||
|  | 	for _, file := range pass.Files { | ||||||
|  | 		filename := pass.Fset.Position(file.Pos()).Filename | ||||||
|  | 		if !strings.HasSuffix(filename, ".go") { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if !includeTests && strings.HasSuffix(filename, "_test.go") { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		fileCommentMap := ast.NewCommentMap(pass.Fset, file, file.Comments) | ||||||
|  | 
 | ||||||
|  | 		for _, decl := range file.Decls { | ||||||
|  | 			genDecl, ok := decl.(*ast.GenDecl) | ||||||
|  | 			if !ok { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			if genDecl.Tok != token.VAR { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			if isAllowed(fileCommentMap, genDecl) { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			for _, spec := range genDecl.Specs { | ||||||
|  | 				valueSpec := spec.(*ast.ValueSpec) | ||||||
|  | 				onlyAllowedValues := false | ||||||
|  | 
 | ||||||
|  | 				for _, vn := range valueSpec.Values { | ||||||
|  | 					if isAllowed(fileCommentMap, vn) { | ||||||
|  | 						onlyAllowedValues = true | ||||||
|  | 						continue | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					onlyAllowedValues = false | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if onlyAllowedValues { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				for _, vn := range valueSpec.Names { | ||||||
|  | 					if isAllowed(fileCommentMap, vn) { | ||||||
|  | 						continue | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					message := fmt.Sprintf("%s is a global variable", vn.Name) | ||||||
|  | 					pass.Report(analysis.Diagnostic{ | ||||||
|  | 						Pos:      vn.Pos(), | ||||||
|  | 						Category: "global", | ||||||
|  | 						Message:  message, | ||||||
|  | 					}) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | MIT License | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2021 Anton Telyshev | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
							
								
								
									
										134
									
								
								tests/tools/vendor/github.com/Antonboom/errname/pkg/analyzer/analyzer.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										134
									
								
								tests/tools/vendor/github.com/Antonboom/errname/pkg/analyzer/analyzer.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,134 @@ | ||||||
|  | package analyzer | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/token" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"unicode" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/go/analysis" | ||||||
|  | 	"golang.org/x/tools/go/analysis/passes/inspect" | ||||||
|  | 	"golang.org/x/tools/go/ast/inspector" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // New returns new errname analyzer.
 | ||||||
|  | func New() *analysis.Analyzer { | ||||||
|  | 	return &analysis.Analyzer{ | ||||||
|  | 		Name:     "errname", | ||||||
|  | 		Doc:      "Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.", | ||||||
|  | 		Run:      run, | ||||||
|  | 		Requires: []*analysis.Analyzer{inspect.Analyzer}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type stringSet = map[string]struct{} | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	imports = []ast.Node{(*ast.ImportSpec)(nil)} | ||||||
|  | 	types   = []ast.Node{(*ast.TypeSpec)(nil)} | ||||||
|  | 	funcs   = []ast.Node{(*ast.FuncDecl)(nil)} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func run(pass *analysis.Pass) (interface{}, error) { | ||||||
|  | 	insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) | ||||||
|  | 
 | ||||||
|  | 	pkgAliases := map[string]string{} | ||||||
|  | 	insp.Preorder(imports, func(node ast.Node) { | ||||||
|  | 		i := node.(*ast.ImportSpec) | ||||||
|  | 		if n := i.Name; n != nil && i.Path != nil { | ||||||
|  | 			if path, err := strconv.Unquote(i.Path.Value); err == nil { | ||||||
|  | 				pkgAliases[n.Name] = getPkgFromPath(path) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	allTypes := stringSet{} | ||||||
|  | 	typesSpecs := map[string]*ast.TypeSpec{} | ||||||
|  | 	insp.Preorder(types, func(node ast.Node) { | ||||||
|  | 		t := node.(*ast.TypeSpec) | ||||||
|  | 		allTypes[t.Name.Name] = struct{}{} | ||||||
|  | 		typesSpecs[t.Name.Name] = t | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	errorTypes := stringSet{} | ||||||
|  | 	insp.Preorder(funcs, func(node ast.Node) { | ||||||
|  | 		f := node.(*ast.FuncDecl) | ||||||
|  | 		t, ok := isMethodError(f) | ||||||
|  | 		if !ok { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		errorTypes[t] = struct{}{} | ||||||
|  | 
 | ||||||
|  | 		tSpec, ok := typesSpecs[t] | ||||||
|  | 		if !ok { | ||||||
|  | 			panic("no specification for type " + t) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if _, ok := tSpec.Type.(*ast.ArrayType); ok { | ||||||
|  | 			if !isValidErrorArrayTypeName(t) { | ||||||
|  | 				reportAboutErrorType(pass, tSpec.Pos(), t, true) | ||||||
|  | 			} | ||||||
|  | 		} else if !isValidErrorTypeName(t) { | ||||||
|  | 			reportAboutErrorType(pass, tSpec.Pos(), t, false) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	errorFuncs := stringSet{} | ||||||
|  | 	insp.Preorder(funcs, func(node ast.Node) { | ||||||
|  | 		f := node.(*ast.FuncDecl) | ||||||
|  | 		if isFuncReturningErr(f.Type, allTypes, errorTypes) { | ||||||
|  | 			errorFuncs[f.Name.Name] = struct{}{} | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	inspectPkgLevelVarsOnly := func(node ast.Node) bool { | ||||||
|  | 		switch v := node.(type) { | ||||||
|  | 		case *ast.FuncDecl: | ||||||
|  | 			return false | ||||||
|  | 
 | ||||||
|  | 		case *ast.ValueSpec: | ||||||
|  | 			if name, ok := isSentinelError(v, pkgAliases, allTypes, errorTypes, errorFuncs); ok && !isValidErrorVarName(name) { | ||||||
|  | 				reportAboutErrorVar(pass, v.Pos(), name) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	for _, f := range pass.Files { | ||||||
|  | 		ast.Inspect(f, inspectPkgLevelVarsOnly) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func reportAboutErrorType(pass *analysis.Pass, typePos token.Pos, typeName string, isArrayType bool) { | ||||||
|  | 	var form string | ||||||
|  | 	if unicode.IsLower([]rune(typeName)[0]) { | ||||||
|  | 		form = "xxxError" | ||||||
|  | 	} else { | ||||||
|  | 		form = "XxxError" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if isArrayType { | ||||||
|  | 		form += "s" | ||||||
|  | 	} | ||||||
|  | 	pass.Reportf(typePos, "the type name `%s` should conform to the `%s` format", typeName, form) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func reportAboutErrorVar(pass *analysis.Pass, pos token.Pos, varName string) { | ||||||
|  | 	var form string | ||||||
|  | 	if unicode.IsLower([]rune(varName)[0]) { | ||||||
|  | 		form = "errXxx" | ||||||
|  | 	} else { | ||||||
|  | 		form = "ErrXxx" | ||||||
|  | 	} | ||||||
|  | 	pass.Reportf(pos, "the variable name `%s` should conform to the `%s` format", varName, form) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getPkgFromPath(p string) string { | ||||||
|  | 	idx := strings.LastIndex(p, "/") | ||||||
|  | 	if idx == -1 { | ||||||
|  | 		return p | ||||||
|  | 	} | ||||||
|  | 	return p[idx+1:] | ||||||
|  | } | ||||||
							
								
								
									
										237
									
								
								tests/tools/vendor/github.com/Antonboom/errname/pkg/analyzer/facts.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										237
									
								
								tests/tools/vendor/github.com/Antonboom/errname/pkg/analyzer/facts.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,237 @@ | ||||||
|  | package analyzer | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/token" | ||||||
|  | 	"strings" | ||||||
|  | 	"unicode" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func isMethodError(f *ast.FuncDecl) (typeName string, ok bool) { | ||||||
|  | 	if f.Recv == nil { | ||||||
|  | 		return "", false | ||||||
|  | 	} | ||||||
|  | 	if f.Name.Name != "Error" { | ||||||
|  | 		return "", false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if f.Type == nil || f.Type.Results == nil || len(f.Type.Results.List) != 1 { | ||||||
|  | 		return "", false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	returnType, ok := f.Type.Results.List[0].Type.(*ast.Ident) | ||||||
|  | 	if !ok { | ||||||
|  | 		return "", false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var receiverType string | ||||||
|  | 
 | ||||||
|  | 	switch rt := f.Recv.List[0].Type.(type) { | ||||||
|  | 	case *ast.Ident: | ||||||
|  | 		receiverType = rt.Name | ||||||
|  | 	case *ast.StarExpr: | ||||||
|  | 		if i, ok := rt.X.(*ast.Ident); ok { | ||||||
|  | 			receiverType = i.Name | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return receiverType, returnType.Name == "string" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isValidErrorTypeName(s string) bool { | ||||||
|  | 	if isInitialism(s) { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	words := split(s) | ||||||
|  | 	wordsCnt := wordsCount(words) | ||||||
|  | 
 | ||||||
|  | 	if wordsCnt["error"] != 1 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return words[len(words)-1] == "error" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isValidErrorArrayTypeName(s string) bool { | ||||||
|  | 	if isInitialism(s) { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	words := split(s) | ||||||
|  | 	wordsCnt := wordsCount(words) | ||||||
|  | 
 | ||||||
|  | 	if wordsCnt["errors"] != 1 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return words[len(words)-1] == "errors" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isFuncReturningErr(fType *ast.FuncType, allTypes, errorTypes stringSet) bool { | ||||||
|  | 	if fType == nil || fType.Results == nil || len(fType.Results.List) != 1 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var returnTypeName string | ||||||
|  | 	switch rt := fType.Results.List[0].Type.(type) { | ||||||
|  | 	case *ast.Ident: | ||||||
|  | 		returnTypeName = rt.Name | ||||||
|  | 	case *ast.StarExpr: | ||||||
|  | 		if i, ok := rt.X.(*ast.Ident); ok { | ||||||
|  | 			returnTypeName = i.Name | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return isErrorType(returnTypeName, allTypes, errorTypes) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isErrorType(tName string, allTypes, errorTypes stringSet) bool { | ||||||
|  | 	_, isUserType := allTypes[tName] | ||||||
|  | 	_, isErrType := errorTypes[tName] | ||||||
|  | 	return isErrType || (tName == "error" && !isUserType) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var knownErrConstructors = stringSet{ | ||||||
|  | 	"fmt.Errorf":           {}, | ||||||
|  | 	"errors.Errorf":        {}, | ||||||
|  | 	"errors.New":           {}, | ||||||
|  | 	"errors.Newf":          {}, | ||||||
|  | 	"errors.NewWithDepth":  {}, | ||||||
|  | 	"errors.NewWithDepthf": {}, | ||||||
|  | 	"errors.NewAssertionErrorWithWrappedErrf": {}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isSentinelError( //nolint:gocognit
 | ||||||
|  | 	v *ast.ValueSpec, | ||||||
|  | 	pkgAliases map[string]string, | ||||||
|  | 	allTypes, errorTypes, errorFuncs stringSet, | ||||||
|  | ) (varName string, ok bool) { | ||||||
|  | 	if len(v.Names) != 1 { | ||||||
|  | 		return "", false | ||||||
|  | 	} | ||||||
|  | 	varName = v.Names[0].Name | ||||||
|  | 
 | ||||||
|  | 	switch vv := v.Type.(type) { | ||||||
|  | 	// var ErrEndOfFile error
 | ||||||
|  | 	// var ErrEndOfFile SomeErrType
 | ||||||
|  | 	case *ast.Ident: | ||||||
|  | 		if isErrorType(vv.Name, allTypes, errorTypes) { | ||||||
|  | 			return varName, true | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	// var ErrEndOfFile *SomeErrType
 | ||||||
|  | 	case *ast.StarExpr: | ||||||
|  | 		if i, ok := vv.X.(*ast.Ident); ok && isErrorType(i.Name, allTypes, errorTypes) { | ||||||
|  | 			return varName, true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(v.Values) != 1 { | ||||||
|  | 		return "", false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch vv := v.Values[0].(type) { | ||||||
|  | 	case *ast.CallExpr: | ||||||
|  | 		switch fun := vv.Fun.(type) { | ||||||
|  | 		// var ErrEndOfFile = errors.New("end of file")
 | ||||||
|  | 		case *ast.SelectorExpr: | ||||||
|  | 			pkg, ok := fun.X.(*ast.Ident) | ||||||
|  | 			if !ok { | ||||||
|  | 				return "", false | ||||||
|  | 			} | ||||||
|  | 			pkgFun := fun.Sel | ||||||
|  | 
 | ||||||
|  | 			pkgName := pkg.Name | ||||||
|  | 			if a, ok := pkgAliases[pkgName]; ok { | ||||||
|  | 				pkgName = a | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			_, ok = knownErrConstructors[pkgName+"."+pkgFun.Name] | ||||||
|  | 			return varName, ok | ||||||
|  | 
 | ||||||
|  | 		// var ErrEndOfFile = newErrEndOfFile()
 | ||||||
|  | 		// var ErrEndOfFile = new(EndOfFileError)
 | ||||||
|  | 		// const ErrEndOfFile = constError("end of file")
 | ||||||
|  | 		case *ast.Ident: | ||||||
|  | 			if isErrorType(fun.Name, allTypes, errorTypes) { | ||||||
|  | 				return varName, true | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if _, ok := errorFuncs[fun.Name]; ok { | ||||||
|  | 				return varName, true | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if fun.Name == "new" && len(vv.Args) == 1 { | ||||||
|  | 				if i, ok := vv.Args[0].(*ast.Ident); ok { | ||||||
|  | 					return varName, isErrorType(i.Name, allTypes, errorTypes) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 		// var ErrEndOfFile = func() error { ... }
 | ||||||
|  | 		case *ast.FuncLit: | ||||||
|  | 			return varName, isFuncReturningErr(fun.Type, allTypes, errorTypes) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	// var ErrEndOfFile = &EndOfFileError{}
 | ||||||
|  | 	case *ast.UnaryExpr: | ||||||
|  | 		if vv.Op == token.AND { // &
 | ||||||
|  | 			if lit, ok := vv.X.(*ast.CompositeLit); ok { | ||||||
|  | 				if i, ok := lit.Type.(*ast.Ident); ok { | ||||||
|  | 					return varName, isErrorType(i.Name, allTypes, errorTypes) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	// var ErrEndOfFile = EndOfFileError{}
 | ||||||
|  | 	case *ast.CompositeLit: | ||||||
|  | 		if i, ok := vv.Type.(*ast.Ident); ok { | ||||||
|  | 			return varName, isErrorType(i.Name, allTypes, errorTypes) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return "", false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isValidErrorVarName(s string) bool { | ||||||
|  | 	if isInitialism(s) { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	words := split(s) | ||||||
|  | 	wordsCnt := wordsCount(words) | ||||||
|  | 
 | ||||||
|  | 	if wordsCnt["err"] != 1 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return words[0] == "err" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isInitialism(s string) bool { | ||||||
|  | 	return strings.ToLower(s) == s || strings.ToUpper(s) == s | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func split(s string) []string { | ||||||
|  | 	var words []string | ||||||
|  | 	ss := []rune(s) | ||||||
|  | 
 | ||||||
|  | 	var b strings.Builder | ||||||
|  | 	b.WriteRune(ss[0]) | ||||||
|  | 
 | ||||||
|  | 	for _, r := range ss[1:] { | ||||||
|  | 		if unicode.IsUpper(r) { | ||||||
|  | 			words = append(words, strings.ToLower(b.String())) | ||||||
|  | 			b.Reset() | ||||||
|  | 		} | ||||||
|  | 		b.WriteRune(r) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	words = append(words, strings.ToLower(b.String())) | ||||||
|  | 	return words | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func wordsCount(w []string) map[string]int { | ||||||
|  | 	result := make(map[string]int, len(w)) | ||||||
|  | 	for _, ww := range w { | ||||||
|  | 		result[ww]++ | ||||||
|  | 	} | ||||||
|  | 	return result | ||||||
|  | } | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | MIT License | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2021 Anton Telyshev | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
							
								
								
									
										148
									
								
								tests/tools/vendor/github.com/Antonboom/nilnil/pkg/analyzer/analyzer.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										148
									
								
								tests/tools/vendor/github.com/Antonboom/nilnil/pkg/analyzer/analyzer.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,148 @@ | ||||||
|  | package analyzer | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"go/ast" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/go/analysis" | ||||||
|  | 	"golang.org/x/tools/go/analysis/passes/inspect" | ||||||
|  | 	"golang.org/x/tools/go/ast/inspector" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	name = "nilnil" | ||||||
|  | 	doc  = "Checks that there is no simultaneous return of `nil` error and an invalid value." | ||||||
|  | 
 | ||||||
|  | 	reportMsg = "return both the `nil` error and invalid value: use a sentinel error instead" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // New returns new nilnil analyzer.
 | ||||||
|  | func New() *analysis.Analyzer { | ||||||
|  | 	n := newNilNil() | ||||||
|  | 
 | ||||||
|  | 	a := &analysis.Analyzer{ | ||||||
|  | 		Name:     name, | ||||||
|  | 		Doc:      doc, | ||||||
|  | 		Run:      n.run, | ||||||
|  | 		Requires: []*analysis.Analyzer{inspect.Analyzer}, | ||||||
|  | 	} | ||||||
|  | 	a.Flags.Var(&n.checkedTypes, "checked-types", "coma separated list") | ||||||
|  | 
 | ||||||
|  | 	return a | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type nilNil struct { | ||||||
|  | 	checkedTypes checkedTypes | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newNilNil() *nilNil { | ||||||
|  | 	return &nilNil{ | ||||||
|  | 		checkedTypes: newDefaultCheckedTypes(), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	types = []ast.Node{(*ast.TypeSpec)(nil)} | ||||||
|  | 
 | ||||||
|  | 	funcAndReturns = []ast.Node{ | ||||||
|  | 		(*ast.FuncDecl)(nil), | ||||||
|  | 		(*ast.FuncLit)(nil), | ||||||
|  | 		(*ast.ReturnStmt)(nil), | ||||||
|  | 	} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type typeSpecByName map[string]*ast.TypeSpec | ||||||
|  | 
 | ||||||
|  | func (n *nilNil) run(pass *analysis.Pass) (interface{}, error) { | ||||||
|  | 	insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) | ||||||
|  | 
 | ||||||
|  | 	typeSpecs := typeSpecByName{} | ||||||
|  | 	insp.Preorder(types, func(node ast.Node) { | ||||||
|  | 		t := node.(*ast.TypeSpec) | ||||||
|  | 		typeSpecs[t.Name.Name] = t | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	var fs funcTypeStack | ||||||
|  | 	insp.Nodes(funcAndReturns, func(node ast.Node, push bool) (proceed bool) { | ||||||
|  | 		switch v := node.(type) { | ||||||
|  | 		case *ast.FuncLit: | ||||||
|  | 			if push { | ||||||
|  | 				fs.Push(v.Type) | ||||||
|  | 			} else { | ||||||
|  | 				fs.Pop() | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 		case *ast.FuncDecl: | ||||||
|  | 			if push { | ||||||
|  | 				fs.Push(v.Type) | ||||||
|  | 			} else { | ||||||
|  | 				fs.Pop() | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 		case *ast.ReturnStmt: | ||||||
|  | 			ft := fs.Top() // Current function.
 | ||||||
|  | 
 | ||||||
|  | 			if !push || len(v.Results) != 2 || ft == nil || ft.Results == nil || len(ft.Results.List) != 2 { | ||||||
|  | 				return false | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			fRes1, fRes2 := ft.Results.List[0], ft.Results.List[1] | ||||||
|  | 			if !(n.isDangerNilField(fRes1, typeSpecs) && n.isErrorField(fRes2)) { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			rRes1, rRes2 := v.Results[0], v.Results[1] | ||||||
|  | 			if isNil(rRes1) && isNil(rRes2) { | ||||||
|  | 				pass.Reportf(v.Pos(), reportMsg) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return true | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *nilNil) isDangerNilField(f *ast.Field, typeSpecs typeSpecByName) bool { | ||||||
|  | 	return n.isDangerNilType(f.Type, typeSpecs) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *nilNil) isDangerNilType(t ast.Expr, typeSpecs typeSpecByName) bool { | ||||||
|  | 	switch v := t.(type) { | ||||||
|  | 	case *ast.StarExpr: | ||||||
|  | 		return n.checkedTypes.Contains(ptrType) | ||||||
|  | 
 | ||||||
|  | 	case *ast.FuncType: | ||||||
|  | 		return n.checkedTypes.Contains(funcType) | ||||||
|  | 
 | ||||||
|  | 	case *ast.InterfaceType: | ||||||
|  | 		return n.checkedTypes.Contains(ifaceType) | ||||||
|  | 
 | ||||||
|  | 	case *ast.MapType: | ||||||
|  | 		return n.checkedTypes.Contains(mapType) | ||||||
|  | 
 | ||||||
|  | 	case *ast.ChanType: | ||||||
|  | 		return n.checkedTypes.Contains(chanType) | ||||||
|  | 
 | ||||||
|  | 	case *ast.Ident: | ||||||
|  | 		if t, ok := typeSpecs[v.Name]; ok { | ||||||
|  | 			return n.isDangerNilType(t.Type, nil) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *nilNil) isErrorField(f *ast.Field) bool { | ||||||
|  | 	return isIdent(f.Type, "error") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isNil(e ast.Expr) bool { | ||||||
|  | 	return isIdent(e, "nil") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isIdent(n ast.Node, name string) bool { | ||||||
|  | 	i, ok := n.(*ast.Ident) | ||||||
|  | 	if !ok { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return i.Name == name | ||||||
|  | } | ||||||
							
								
								
									
										77
									
								
								tests/tools/vendor/github.com/Antonboom/nilnil/pkg/analyzer/config.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										77
									
								
								tests/tools/vendor/github.com/Antonboom/nilnil/pkg/analyzer/config.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,77 @@ | ||||||
|  | package analyzer | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"sort" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func newDefaultCheckedTypes() checkedTypes { | ||||||
|  | 	return checkedTypes{ | ||||||
|  | 		ptrType:   struct{}{}, | ||||||
|  | 		funcType:  struct{}{}, | ||||||
|  | 		ifaceType: struct{}{}, | ||||||
|  | 		mapType:   struct{}{}, | ||||||
|  | 		chanType:  struct{}{}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const separator = ',' | ||||||
|  | 
 | ||||||
|  | type typeName string | ||||||
|  | 
 | ||||||
|  | func (t typeName) S() string { | ||||||
|  | 	return string(t) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	ptrType   typeName = "ptr" | ||||||
|  | 	funcType  typeName = "func" | ||||||
|  | 	ifaceType typeName = "iface" | ||||||
|  | 	mapType   typeName = "map" | ||||||
|  | 	chanType  typeName = "chan" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var knownTypes = []typeName{ptrType, funcType, ifaceType, mapType, chanType} | ||||||
|  | 
 | ||||||
|  | type checkedTypes map[typeName]struct{} | ||||||
|  | 
 | ||||||
|  | func (c checkedTypes) Contains(t typeName) bool { | ||||||
|  | 	_, ok := c[t] | ||||||
|  | 	return ok | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c checkedTypes) String() string { | ||||||
|  | 	result := make([]string, 0, len(c)) | ||||||
|  | 	for t := range c { | ||||||
|  | 		result = append(result, t.S()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sort.Strings(result) | ||||||
|  | 	return strings.Join(result, string(separator)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c checkedTypes) Set(s string) error { | ||||||
|  | 	types := strings.FieldsFunc(s, func(c rune) bool { return c == separator }) | ||||||
|  | 	if len(types) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	c.disableAll() | ||||||
|  | 	for _, t := range types { | ||||||
|  | 		switch tt := typeName(t); tt { | ||||||
|  | 		case ptrType, funcType, ifaceType, mapType, chanType: | ||||||
|  | 			c[tt] = struct{}{} | ||||||
|  | 		default: | ||||||
|  | 			return fmt.Errorf("unknown checked type name %q (see help)", t) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c checkedTypes) disableAll() { | ||||||
|  | 	for k := range c { | ||||||
|  | 		delete(c, k) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										29
									
								
								tests/tools/vendor/github.com/Antonboom/nilnil/pkg/analyzer/func_type_stack.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										29
									
								
								tests/tools/vendor/github.com/Antonboom/nilnil/pkg/analyzer/func_type_stack.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,29 @@ | ||||||
|  | package analyzer | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"go/ast" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type funcTypeStack []*ast.FuncType | ||||||
|  | 
 | ||||||
|  | func (s *funcTypeStack) Push(f *ast.FuncType) { | ||||||
|  | 	*s = append(*s, f) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *funcTypeStack) Pop() *ast.FuncType { | ||||||
|  | 	if len(*s) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	last := len(*s) - 1 | ||||||
|  | 	f := (*s)[last] | ||||||
|  | 	*s = (*s)[:last] | ||||||
|  | 	return f | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *funcTypeStack) Top() *ast.FuncType { | ||||||
|  | 	if len(*s) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return (*s)[len(*s)-1] | ||||||
|  | } | ||||||
|  | @ -1,5 +1,2 @@ | ||||||
| TAGS |  | ||||||
| tags |  | ||||||
| .*.swp |  | ||||||
| tomlcheck/tomlcheck |  | ||||||
| toml.test | toml.test | ||||||
|  | /toml-test | ||||||
|  |  | ||||||
|  | @ -1,15 +0,0 @@ | ||||||
| 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 |  | ||||||
|  | @ -1,3 +1 @@ | ||||||
| Compatible with TOML version | Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0). | ||||||
| [v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md) |  | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -1,19 +0,0 @@ | ||||||
| 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 |  | ||||||
| 
 |  | ||||||
|  | @ -6,27 +6,22 @@ packages. This package also supports the `encoding.TextUnmarshaler` and | ||||||
| `encoding.TextMarshaler` interfaces so that you can define custom data | `encoding.TextMarshaler` interfaces so that you can define custom data | ||||||
| representations. (There is an example of this below.) | representations. (There is an example of this below.) | ||||||
| 
 | 
 | ||||||
| Spec: https://github.com/toml-lang/toml | Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0). | ||||||
| 
 | 
 | ||||||
| Compatible with TOML version | Documentation: https://godocs.io/github.com/BurntSushi/toml | ||||||
| [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 | See the [releases page](https://github.com/BurntSushi/toml/releases) for a | ||||||
|  | changelog; this information is also in the git tag annotations (e.g. `git show | ||||||
|  | v0.4.0`). | ||||||
| 
 | 
 | ||||||
| Installation: | This library requires Go 1.13 or newer; install it with: | ||||||
| 
 | 
 | ||||||
| ```bash |     $ go get github.com/BurntSushi/toml | ||||||
| go get github.com/BurntSushi/toml |  | ||||||
| ``` |  | ||||||
| 
 | 
 | ||||||
| Try the toml validator: | It also comes with a TOML validator CLI tool: | ||||||
| 
 | 
 | ||||||
| ```bash |     $ go get github.com/BurntSushi/toml/cmd/tomlv | ||||||
| go get github.com/BurntSushi/toml/cmd/tomlv |     $ tomlv some-toml-file.toml | ||||||
| tomlv some-toml-file.toml |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| [](https://travis-ci.org/BurntSushi/toml) [](https://godoc.org/github.com/BurntSushi/toml) |  | ||||||
| 
 | 
 | ||||||
| ### Testing | ### Testing | ||||||
| 
 | 
 | ||||||
|  | @ -36,8 +31,8 @@ and the encoder. | ||||||
| 
 | 
 | ||||||
| ### Examples | ### Examples | ||||||
| 
 | 
 | ||||||
| This package works similarly to how the Go standard library handles `XML` | This package works similarly to how the Go standard library handles XML and | ||||||
| and `JSON`. Namely, data is loaded into Go values via reflection. | JSON. Namely, data is loaded into Go values via reflection. | ||||||
| 
 | 
 | ||||||
| For the simplest example, consider some TOML file as just a list of keys | For the simplest example, consider some TOML file as just a list of keys | ||||||
| and values: | and values: | ||||||
|  | @ -54,11 +49,11 @@ Which could be defined in Go as: | ||||||
| 
 | 
 | ||||||
| ```go | ```go | ||||||
| type Config struct { | type Config struct { | ||||||
|   Age int | 	Age        int | ||||||
|   Cats []string | 	Cats       []string | ||||||
|   Pi float64 | 	Pi         float64 | ||||||
|   Perfection []int | 	Perfection []int | ||||||
|   DOB time.Time // requires `import time` | 	DOB        time.Time // requires `import time` | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | @ -84,6 +79,9 @@ type TOML struct { | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | Beware that like other most other decoders **only exported fields** are | ||||||
|  | considered when encoding and decoding; private fields are silently ignored. | ||||||
|  | 
 | ||||||
| ### Using the `encoding.TextUnmarshaler` interface | ### Using the `encoding.TextUnmarshaler` interface | ||||||
| 
 | 
 | ||||||
| Here's an example that automatically parses duration strings into | Here's an example that automatically parses duration strings into | ||||||
|  | @ -103,19 +101,19 @@ Which can be decoded with: | ||||||
| 
 | 
 | ||||||
| ```go | ```go | ||||||
| type song struct { | type song struct { | ||||||
|   Name     string | 	Name     string | ||||||
|   Duration duration | 	Duration duration | ||||||
| } | } | ||||||
| type songs struct { | type songs struct { | ||||||
|   Song []song | 	Song []song | ||||||
| } | } | ||||||
| var favorites songs | var favorites songs | ||||||
| if _, err := toml.Decode(blob, &favorites); err != nil { | if _, err := toml.Decode(blob, &favorites); err != nil { | ||||||
|   log.Fatal(err) | 	log.Fatal(err) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| for _, s := range favorites.Song { | for _, s := range favorites.Song { | ||||||
|   fmt.Printf("%s (%s)\n", s.Name, s.Duration) | 	fmt.Printf("%s (%s)\n", s.Name, s.Duration) | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | @ -134,6 +132,9 @@ func (d *duration) UnmarshalText(text []byte) error { | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | To target TOML specifically you can implement `UnmarshalTOML` TOML interface in | ||||||
|  | a similar way. | ||||||
|  | 
 | ||||||
| ### More complex usage | ### More complex usage | ||||||
| 
 | 
 | ||||||
| Here's an example of how to load the example from the official spec page: | Here's an example of how to load the example from the official spec page: | ||||||
|  | @ -180,23 +181,23 @@ And the corresponding Go types are: | ||||||
| 
 | 
 | ||||||
| ```go | ```go | ||||||
| type tomlConfig struct { | type tomlConfig struct { | ||||||
| 	Title string | 	Title   string | ||||||
| 	Owner ownerInfo | 	Owner   ownerInfo | ||||||
| 	DB database `toml:"database"` | 	DB      database `toml:"database"` | ||||||
| 	Servers map[string]server | 	Servers map[string]server | ||||||
| 	Clients clients | 	Clients clients | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type ownerInfo struct { | type ownerInfo struct { | ||||||
| 	Name string | 	Name string | ||||||
| 	Org string `toml:"organization"` | 	Org  string `toml:"organization"` | ||||||
| 	Bio string | 	Bio  string | ||||||
| 	DOB time.Time | 	DOB  time.Time | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type database struct { | type database struct { | ||||||
| 	Server string | 	Server  string | ||||||
| 	Ports []int | 	Ports   []int | ||||||
| 	ConnMax int `toml:"connection_max"` | 	ConnMax int `toml:"connection_max"` | ||||||
| 	Enabled bool | 	Enabled bool | ||||||
| } | } | ||||||
|  | @ -207,7 +208,7 @@ type server struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type clients struct { | type clients struct { | ||||||
| 	Data [][]interface{} | 	Data  [][]interface{} | ||||||
| 	Hosts []string | 	Hosts []string | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
|  | @ -216,3 +217,4 @@ Note that a case insensitive match will be tried if an exact match can't be | ||||||
| found. | found. | ||||||
| 
 | 
 | ||||||
| A working example of the above can be found in `_examples/example.{go,toml}`. | A working example of the above can be found in `_examples/example.{go,toml}`. | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | @ -1,19 +1,17 @@ | ||||||
| package toml | package toml | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"encoding" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"math" | 	"math" | ||||||
|  | 	"os" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"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
 | // Unmarshaler is the interface implemented by objects that can unmarshal a
 | ||||||
| // TOML description of themselves.
 | // TOML description of themselves.
 | ||||||
| type Unmarshaler interface { | type Unmarshaler interface { | ||||||
|  | @ -27,30 +25,21 @@ func Unmarshal(p []byte, v interface{}) error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Primitive is a TOML value that hasn't been decoded into a Go value.
 | // 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.
 | // This type can be used for any value, which will cause decoding to be delayed.
 | ||||||
|  | // You can use the PrimitiveDecode() function to "manually" decode these values.
 | ||||||
| //
 | //
 | ||||||
| // The underlying representation of a `Primitive` value is subject to change.
 | // NOTE: The underlying representation of a `Primitive` value is subject to
 | ||||||
| // Do not rely on it.
 | // change. Do not rely on it.
 | ||||||
| //
 | //
 | ||||||
| // N.B. Primitive values are still parsed, so using them will only avoid
 | // NOTE: Primitive values are still parsed, so using them will only avoid the
 | ||||||
| // the overhead of reflection. They can be useful when you don't know the
 | // overhead of reflection. They can be useful when you don't know the exact type
 | ||||||
| // exact type of TOML data until run time.
 | // of TOML data until runtime.
 | ||||||
| type Primitive struct { | type Primitive struct { | ||||||
| 	undecoded interface{} | 	undecoded interface{} | ||||||
| 	context   Key | 	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
 | // PrimitiveDecode is just like the other `Decode*` functions, except it
 | ||||||
| // decodes a TOML value that has already been parsed. Valid primitive values
 | // decodes a TOML value that has already been parsed. Valid primitive values
 | ||||||
| // can *only* be obtained from values filled by the decoder functions,
 | // can *only* be obtained from values filled by the decoder functions,
 | ||||||
|  | @ -68,43 +57,51 @@ func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error { | ||||||
| 	return md.unify(primValue.undecoded, rvalue(v)) | 	return md.unify(primValue.undecoded, rvalue(v)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Decode will decode the contents of `data` in TOML format into a pointer
 | // Decoder decodes TOML data.
 | ||||||
| // `v`.
 |  | ||||||
| //
 | //
 | ||||||
| // TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be
 | // TOML tables correspond to Go structs or maps (dealer's choice – they can be
 | ||||||
| // used interchangeably.)
 | // used interchangeably).
 | ||||||
| //
 | //
 | ||||||
| // TOML arrays of tables correspond to either a slice of structs or a slice
 | // TOML table arrays correspond to either a slice of structs or a slice of maps.
 | ||||||
| // of maps.
 |  | ||||||
| //
 | //
 | ||||||
| // TOML datetimes correspond to Go `time.Time` values.
 | // TOML datetimes correspond to Go time.Time values. Local datetimes are parsed
 | ||||||
|  | // in the local timezone.
 | ||||||
| //
 | //
 | ||||||
| // All other TOML types (float, string, int, bool and array) correspond
 | // All other TOML types (float, string, int, bool and array) correspond to the
 | ||||||
| // to the obvious Go types.
 | // obvious Go types.
 | ||||||
| //
 | //
 | ||||||
| // An exception to the above rules is if a type implements the
 | // An exception to the above rules is if a type implements the TextUnmarshaler
 | ||||||
| // encoding.TextUnmarshaler interface. In this case, any primitive TOML value
 | // interface, in which case any primitive TOML value (floats, strings, integers,
 | ||||||
| // (floats, strings, integers, booleans and datetimes) will be converted to
 | // booleans, datetimes) will be converted to a []byte and given to the value's
 | ||||||
| // a byte string and given to the value's UnmarshalText method. See the
 | // UnmarshalText method. See the Unmarshaler example for a demonstration with
 | ||||||
| // Unmarshaler example for a demonstration with time duration strings.
 | // time duration strings.
 | ||||||
| //
 | //
 | ||||||
| // Key mapping
 | // Key mapping
 | ||||||
| //
 | //
 | ||||||
| // TOML keys can map to either keys in a Go map or field names in a Go
 | // TOML keys can map to either keys in a Go map or field names in a Go struct.
 | ||||||
| // struct. The special `toml` struct tag may be used to map TOML keys to
 | // The special `toml` struct tag can be used to map TOML keys to struct fields
 | ||||||
| // struct fields that don't match the key name exactly. (See the example.)
 | // that don't match the key name exactly (see the example). A case insensitive
 | ||||||
| // A case insensitive match to struct names will be tried if an exact match
 | // match to struct names will be tried if an exact match can't be found.
 | ||||||
| // can't be found.
 |  | ||||||
| //
 | //
 | ||||||
| // The mapping between TOML values and Go values is loose. That is, there
 | // The mapping between TOML values and Go values is loose. That is, there may
 | ||||||
| // may exist TOML values that cannot be placed into your representation, and
 | // exist TOML values that cannot be placed into your representation, and there
 | ||||||
| // there may be parts of your representation that do not correspond to
 | // may be parts of your representation that do not correspond to TOML values.
 | ||||||
| // TOML values. This loose mapping can be made stricter by using the IsDefined
 | // This loose mapping can be made stricter by using the IsDefined and/or
 | ||||||
| // and/or Undecoded methods on the MetaData returned.
 | // Undecoded methods on the MetaData returned.
 | ||||||
| //
 | //
 | ||||||
| // This decoder will not handle cyclic types. If a cyclic type is passed,
 | // This decoder does not handle cyclic types. Decode will not terminate if a
 | ||||||
| // `Decode` will not terminate.
 | // cyclic type is passed.
 | ||||||
| func Decode(data string, v interface{}) (MetaData, error) { | type Decoder struct { | ||||||
|  | 	r io.Reader | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewDecoder creates a new Decoder.
 | ||||||
|  | func NewDecoder(r io.Reader) *Decoder { | ||||||
|  | 	return &Decoder{r: r} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Decode TOML data in to the pointer `v`.
 | ||||||
|  | func (dec *Decoder) Decode(v interface{}) (MetaData, error) { | ||||||
| 	rv := reflect.ValueOf(v) | 	rv := reflect.ValueOf(v) | ||||||
| 	if rv.Kind() != reflect.Ptr { | 	if rv.Kind() != reflect.Ptr { | ||||||
| 		return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v)) | 		return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v)) | ||||||
|  | @ -112,7 +109,15 @@ func Decode(data string, v interface{}) (MetaData, error) { | ||||||
| 	if rv.IsNil() { | 	if rv.IsNil() { | ||||||
| 		return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v)) | 		return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v)) | ||||||
| 	} | 	} | ||||||
| 	p, err := parse(data) | 
 | ||||||
|  | 	// TODO: have parser should read from io.Reader? Or at the very least, make
 | ||||||
|  | 	// it read from []byte rather than string
 | ||||||
|  | 	data, err := ioutil.ReadAll(dec.r) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return MetaData{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	p, err := parse(string(data)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return MetaData{}, err | 		return MetaData{}, err | ||||||
| 	} | 	} | ||||||
|  | @ -123,24 +128,22 @@ func Decode(data string, v interface{}) (MetaData, error) { | ||||||
| 	return md, md.unify(p.mapping, indirect(rv)) | 	return md, md.unify(p.mapping, indirect(rv)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // DecodeFile is just like Decode, except it will automatically read the
 | // Decode the TOML data in to the pointer v.
 | ||||||
| // contents of the file at `fpath` and decode it for you.
 | //
 | ||||||
| func DecodeFile(fpath string, v interface{}) (MetaData, error) { | // See the documentation on Decoder for a description of the decoding process.
 | ||||||
| 	bs, err := ioutil.ReadFile(fpath) | func Decode(data string, v interface{}) (MetaData, error) { | ||||||
| 	if err != nil { | 	return NewDecoder(strings.NewReader(data)).Decode(v) | ||||||
| 		return MetaData{}, err |  | ||||||
| 	} |  | ||||||
| 	return Decode(string(bs), v) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // DecodeReader is just like Decode, except it will consume all bytes
 | // DecodeFile is just like Decode, except it will automatically read the
 | ||||||
| // from the reader and decode it for you.
 | // contents of the file at path and decode it for you.
 | ||||||
| func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { | func DecodeFile(path string, v interface{}) (MetaData, error) { | ||||||
| 	bs, err := ioutil.ReadAll(r) | 	fp, err := os.Open(path) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return MetaData{}, err | 		return MetaData{}, err | ||||||
| 	} | 	} | ||||||
| 	return Decode(string(bs), v) | 	defer fp.Close() | ||||||
|  | 	return NewDecoder(fp).Decode(v) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // unify performs a sort of type unification based on the structure of `rv`,
 | // unify performs a sort of type unification based on the structure of `rv`,
 | ||||||
|  | @ -149,8 +152,8 @@ func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { | ||||||
| // Any type mismatch produces an error. Finding a type that we don't know
 | // Any type mismatch produces an error. Finding a type that we don't know
 | ||||||
| // how to handle produces an unsupported type error.
 | // how to handle produces an unsupported type error.
 | ||||||
| func (md *MetaData) unify(data interface{}, rv reflect.Value) error { | func (md *MetaData) unify(data interface{}, rv reflect.Value) error { | ||||||
| 
 |  | ||||||
| 	// Special case. Look for a `Primitive` value.
 | 	// Special case. Look for a `Primitive` value.
 | ||||||
|  | 	// TODO: #76 would make this superfluous after implemented.
 | ||||||
| 	if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() { | 	if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() { | ||||||
| 		// Save the undecoded data and the key context into the primitive
 | 		// Save the undecoded data and the key context into the primitive
 | ||||||
| 		// value.
 | 		// value.
 | ||||||
|  | @ -170,25 +173,17 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// 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.
 | 	// Special case. Look for a value satisfying the TextUnmarshaler interface.
 | ||||||
| 	if v, ok := rv.Interface().(TextUnmarshaler); ok { | 	if v, ok := rv.Interface().(encoding.TextUnmarshaler); ok { | ||||||
| 		return md.unifyText(data, v) | 		return md.unifyText(data, v) | ||||||
| 	} | 	} | ||||||
| 	// BUG(burntsushi)
 | 	// TODO:
 | ||||||
| 	// The behavior here is incorrect whenever a Go type satisfies the
 | 	// The behavior here is incorrect whenever a Go type satisfies the
 | ||||||
| 	// encoding.TextUnmarshaler interface but also corresponds to a TOML
 | 	// encoding.TextUnmarshaler interface but also corresponds to a TOML hash or
 | ||||||
| 	// hash or array. In particular, the unmarshaler should only be applied
 | 	// array. In particular, the unmarshaler should only be applied to primitive
 | ||||||
| 	// to primitive TOML values. But at this point, it will be applied to
 | 	// TOML values. But at this point, it will be applied to all kinds of values
 | ||||||
| 	// all kinds of values and produce an incorrect error whenever those values
 | 	// and produce an incorrect error whenever those values are hashes or arrays
 | ||||||
| 	// are hashes or arrays (including arrays of tables).
 | 	// (including arrays of tables).
 | ||||||
| 
 | 
 | ||||||
| 	k := rv.Kind() | 	k := rv.Kind() | ||||||
| 
 | 
 | ||||||
|  | @ -277,6 +272,12 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error { | func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error { | ||||||
|  | 	if k := rv.Type().Key().Kind(); k != reflect.String { | ||||||
|  | 		return fmt.Errorf( | ||||||
|  | 			"toml: cannot decode to a map with non-string key type (%s in %q)", | ||||||
|  | 			k, rv.Type()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	tmap, ok := mapping.(map[string]interface{}) | 	tmap, ok := mapping.(map[string]interface{}) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		if tmap == nil { | 		if tmap == nil { | ||||||
|  | @ -312,10 +313,8 @@ func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error { | ||||||
| 		} | 		} | ||||||
| 		return badtype("slice", data) | 		return badtype("slice", data) | ||||||
| 	} | 	} | ||||||
| 	sliceLen := datav.Len() | 	if l := datav.Len(); l != rv.Len() { | ||||||
| 	if sliceLen != rv.Len() { | 		return e("expected array length %d; got TOML array of length %d", rv.Len(), l) | ||||||
| 		return e("expected array length %d; got TOML array of length %d", |  | ||||||
| 			rv.Len(), sliceLen) |  | ||||||
| 	} | 	} | ||||||
| 	return md.unifySliceArray(datav, rv) | 	return md.unifySliceArray(datav, rv) | ||||||
| } | } | ||||||
|  | @ -337,11 +336,10 @@ func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (md *MetaData) unifySliceArray(data, rv reflect.Value) error { | func (md *MetaData) unifySliceArray(data, rv reflect.Value) error { | ||||||
| 	sliceLen := data.Len() | 	l := data.Len() | ||||||
| 	for i := 0; i < sliceLen; i++ { | 	for i := 0; i < l; i++ { | ||||||
| 		v := data.Index(i).Interface() | 		err := md.unify(data.Index(i).Interface(), indirect(rv.Index(i))) | ||||||
| 		sliceval := indirect(rv.Index(i)) | 		if err != nil { | ||||||
| 		if err := md.unify(v, sliceval); err != nil { |  | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -439,7 +437,7 @@ func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error { | func (md *MetaData) unifyText(data interface{}, v encoding.TextUnmarshaler) error { | ||||||
| 	var s string | 	var s string | ||||||
| 	switch sdata := data.(type) { | 	switch sdata := data.(type) { | ||||||
| 	case TextMarshaler: | 	case TextMarshaler: | ||||||
|  | @ -482,7 +480,7 @@ func indirect(v reflect.Value) reflect.Value { | ||||||
| 	if v.Kind() != reflect.Ptr { | 	if v.Kind() != reflect.Ptr { | ||||||
| 		if v.CanSet() { | 		if v.CanSet() { | ||||||
| 			pv := v.Addr() | 			pv := v.Addr() | ||||||
| 			if _, ok := pv.Interface().(TextUnmarshaler); ok { | 			if _, ok := pv.Interface().(encoding.TextUnmarshaler); ok { | ||||||
| 				return pv | 				return pv | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | @ -498,12 +496,16 @@ func isUnifiable(rv reflect.Value) bool { | ||||||
| 	if rv.CanSet() { | 	if rv.CanSet() { | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 	if _, ok := rv.Interface().(TextUnmarshaler); ok { | 	if _, ok := rv.Interface().(encoding.TextUnmarshaler); ok { | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func e(format string, args ...interface{}) error { | ||||||
|  | 	return fmt.Errorf("toml: "+format, args...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func badtype(expected string, data interface{}) error { | func badtype(expected string, data interface{}) error { | ||||||
| 	return e("cannot load TOML value of type %T into a Go %s", data, expected) | 	return e("cannot load TOML value of type %T into a Go %s", data, expected) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | // +build go1.16
 | ||||||
|  | 
 | ||||||
|  | package toml | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"io/fs" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // DecodeFS is just like Decode, except it will automatically read the contents
 | ||||||
|  | // of the file at `path` from a fs.FS instance.
 | ||||||
|  | func DecodeFS(fsys fs.FS, path string, v interface{}) (MetaData, error) { | ||||||
|  | 	fp, err := fsys.Open(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return MetaData{}, err | ||||||
|  | 	} | ||||||
|  | 	defer fp.Close() | ||||||
|  | 	return NewDecoder(fp).Decode(v) | ||||||
|  | } | ||||||
|  | @ -2,9 +2,9 @@ package toml | ||||||
| 
 | 
 | ||||||
| import "strings" | import "strings" | ||||||
| 
 | 
 | ||||||
| // MetaData allows access to meta information about TOML data that may not
 | // MetaData allows access to meta information about TOML data that may not be
 | ||||||
| // be inferrable via reflection. In particular, whether a key has been defined
 | // inferable via reflection. In particular, whether a key has been defined and
 | ||||||
| // and the TOML type of a key.
 | // the TOML type of a key.
 | ||||||
| type MetaData struct { | type MetaData struct { | ||||||
| 	mapping map[string]interface{} | 	mapping map[string]interface{} | ||||||
| 	types   map[string]tomlType | 	types   map[string]tomlType | ||||||
|  | @ -13,10 +13,11 @@ type MetaData struct { | ||||||
| 	context Key // Used only during decoding.
 | 	context Key // Used only during decoding.
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // IsDefined returns true if the key given exists in the TOML data. The key
 | // IsDefined reports if the key exists in the TOML data.
 | ||||||
| // should be specified hierarchially. e.g.,
 | //
 | ||||||
|  | // The key should be specified hierarchically, for example to access the TOML
 | ||||||
|  | // key "a.b.c" you would use:
 | ||||||
| //
 | //
 | ||||||
| //	// access the TOML key 'a.b.c'
 |  | ||||||
| //	IsDefined("a", "b", "c")
 | //	IsDefined("a", "b", "c")
 | ||||||
| //
 | //
 | ||||||
| // IsDefined will return false if an empty key given. Keys are case sensitive.
 | // IsDefined will return false if an empty key given. Keys are case sensitive.
 | ||||||
|  | @ -41,8 +42,8 @@ func (md *MetaData) IsDefined(key ...string) bool { | ||||||
| 
 | 
 | ||||||
| // Type returns a string representation of the type of the key specified.
 | // 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
 | // Type will return the empty string if given an empty key or a key that does
 | ||||||
| // does not exist. Keys are case sensitive.
 | // not exist. Keys are case sensitive.
 | ||||||
| func (md *MetaData) Type(key ...string) string { | func (md *MetaData) Type(key ...string) string { | ||||||
| 	fullkey := strings.Join(key, ".") | 	fullkey := strings.Join(key, ".") | ||||||
| 	if typ, ok := md.types[fullkey]; ok { | 	if typ, ok := md.types[fullkey]; ok { | ||||||
|  | @ -51,13 +52,11 @@ func (md *MetaData) Type(key ...string) string { | ||||||
| 	return "" | 	return "" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Key is the type of any TOML key, including key groups. Use (MetaData).Keys
 | // Key represents any TOML key, including key groups. Use (MetaData).Keys to get
 | ||||||
| // to get values of this type.
 | // values of this type.
 | ||||||
| type Key []string | type Key []string | ||||||
| 
 | 
 | ||||||
| func (k Key) String() string { | func (k Key) String() string { return strings.Join(k, ".") } | ||||||
| 	return strings.Join(k, ".") |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| func (k Key) maybeQuotedAll() string { | func (k Key) maybeQuotedAll() string { | ||||||
| 	var ss []string | 	var ss []string | ||||||
|  | @ -68,6 +67,9 @@ func (k Key) maybeQuotedAll() string { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (k Key) maybeQuoted(i int) string { | func (k Key) maybeQuoted(i int) string { | ||||||
|  | 	if k[i] == "" { | ||||||
|  | 		return `""` | ||||||
|  | 	} | ||||||
| 	quote := false | 	quote := false | ||||||
| 	for _, c := range k[i] { | 	for _, c := range k[i] { | ||||||
| 		if !isBareKeyChar(c) { | 		if !isBareKeyChar(c) { | ||||||
|  | @ -76,7 +78,7 @@ func (k Key) maybeQuoted(i int) string { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if quote { | 	if quote { | ||||||
| 		return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\"" | 		return `"` + quotedReplacer.Replace(k[i]) + `"` | ||||||
| 	} | 	} | ||||||
| 	return k[i] | 	return k[i] | ||||||
| } | } | ||||||
|  | @ -89,10 +91,10 @@ func (k Key) add(piece string) Key { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Keys returns a slice of every key in the TOML data, including key groups.
 | // 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.
 | // 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.
 | // All keys returned are non-empty.
 | ||||||
| func (md *MetaData) Keys() []Key { | func (md *MetaData) Keys() []Key { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,33 @@ | ||||||
|  | package toml | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding" | ||||||
|  | 	"io" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // DEPRECATED!
 | ||||||
|  | //
 | ||||||
|  | // Use the identical encoding.TextMarshaler instead. It is defined here to
 | ||||||
|  | // support Go 1.1 and older.
 | ||||||
|  | type TextMarshaler encoding.TextMarshaler | ||||||
|  | 
 | ||||||
|  | // DEPRECATED!
 | ||||||
|  | //
 | ||||||
|  | // Use the identical encoding.TextUnmarshaler instead. It is defined here to
 | ||||||
|  | // support Go 1.1 and older.
 | ||||||
|  | type TextUnmarshaler encoding.TextUnmarshaler | ||||||
|  | 
 | ||||||
|  | // 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)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DEPRECATED!
 | ||||||
|  | //
 | ||||||
|  | // Use NewDecoder(reader).Decode(&v) instead.
 | ||||||
|  | func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { | ||||||
|  | 	return NewDecoder(r).Decode(v) | ||||||
|  | } | ||||||
|  | @ -1,27 +1,13 @@ | ||||||
| /* | /* | ||||||
| Package toml provides facilities for decoding and encoding TOML configuration | Package toml implements decoding and encoding of TOML files. | ||||||
| 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
 | This package supports TOML v1.0.0, as listed on https://toml.io
 | ||||||
| 
 | 
 | ||||||
| The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify | There is also support for delaying decoding with the Primitive type, and | ||||||
| whether a file is a valid TOML document. It can also be used to print the | querying the set of keys in a TOML document with the MetaData type. | ||||||
| type of each key in a TOML document. |  | ||||||
| 
 | 
 | ||||||
| Testing | The github.com/BurntSushi/toml/cmd/tomlv package implements a TOML validator, | ||||||
| 
 | and can be used to verify if TOML document is valid. It can also be used to | ||||||
| There are two important types of tests used for this package. The first is | print the type of each key. | ||||||
| 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 | package toml | ||||||
|  |  | ||||||
|  | @ -2,48 +2,92 @@ package toml | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bufio" | 	"bufio" | ||||||
|  | 	"encoding" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"math" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"sort" | 	"sort" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/BurntSushi/toml/internal" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type tomlEncodeError struct{ error } | type tomlEncodeError struct{ error } | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
| 	errArrayMixedElementTypes = errors.New( | 	errArrayNilElement = errors.New("toml: cannot encode array with nil element") | ||||||
| 		"toml: cannot encode array with mixed element types") | 	errNonString       = errors.New("toml: cannot encode a map with non-string key type") | ||||||
| 	errArrayNilElement = errors.New( | 	errAnonNonStruct   = errors.New("toml: cannot encode an anonymous field that is not a struct") | ||||||
| 		"toml: cannot encode array with nil element") | 	errNoKey           = errors.New("toml: top-level values must be Go maps or structs") | ||||||
| 	errNonString = errors.New( | 	errAnything        = errors.New("") // used in testing
 | ||||||
| 		"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( | var quotedReplacer = strings.NewReplacer( | ||||||
| 	"\t", "\\t", |  | ||||||
| 	"\n", "\\n", |  | ||||||
| 	"\r", "\\r", |  | ||||||
| 	"\"", "\\\"", | 	"\"", "\\\"", | ||||||
| 	"\\", "\\\\", | 	"\\", "\\\\", | ||||||
|  | 	"\x00", `\u0000`, | ||||||
|  | 	"\x01", `\u0001`, | ||||||
|  | 	"\x02", `\u0002`, | ||||||
|  | 	"\x03", `\u0003`, | ||||||
|  | 	"\x04", `\u0004`, | ||||||
|  | 	"\x05", `\u0005`, | ||||||
|  | 	"\x06", `\u0006`, | ||||||
|  | 	"\x07", `\u0007`, | ||||||
|  | 	"\b", `\b`, | ||||||
|  | 	"\t", `\t`, | ||||||
|  | 	"\n", `\n`, | ||||||
|  | 	"\x0b", `\u000b`, | ||||||
|  | 	"\f", `\f`, | ||||||
|  | 	"\r", `\r`, | ||||||
|  | 	"\x0e", `\u000e`, | ||||||
|  | 	"\x0f", `\u000f`, | ||||||
|  | 	"\x10", `\u0010`, | ||||||
|  | 	"\x11", `\u0011`, | ||||||
|  | 	"\x12", `\u0012`, | ||||||
|  | 	"\x13", `\u0013`, | ||||||
|  | 	"\x14", `\u0014`, | ||||||
|  | 	"\x15", `\u0015`, | ||||||
|  | 	"\x16", `\u0016`, | ||||||
|  | 	"\x17", `\u0017`, | ||||||
|  | 	"\x18", `\u0018`, | ||||||
|  | 	"\x19", `\u0019`, | ||||||
|  | 	"\x1a", `\u001a`, | ||||||
|  | 	"\x1b", `\u001b`, | ||||||
|  | 	"\x1c", `\u001c`, | ||||||
|  | 	"\x1d", `\u001d`, | ||||||
|  | 	"\x1e", `\u001e`, | ||||||
|  | 	"\x1f", `\u001f`, | ||||||
|  | 	"\x7f", `\u007f`, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Encoder controls the encoding of Go values to a TOML document to some
 | // Encoder encodes a Go to a TOML document.
 | ||||||
| // io.Writer.
 |  | ||||||
| //
 | //
 | ||||||
| // The indentation level can be controlled with the Indent field.
 | // 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 (Go maps or structs), keys without any sub-hashes
 | ||||||
|  | // are encoded first.
 | ||||||
|  | //
 | ||||||
|  | // Go maps will be sorted alphabetically by key for deterministic output.
 | ||||||
|  | //
 | ||||||
|  | // Encoding Go values without a corresponding TOML representation will return an
 | ||||||
|  | // error. Examples of this includes maps with non-string keys, 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 okay, as is []map[string][]string).
 | ||||||
|  | //
 | ||||||
|  | // NOTE: Only exported keys are encoded due to the use of reflection. Unexported
 | ||||||
|  | // keys are silently discarded.
 | ||||||
| type Encoder struct { | type Encoder struct { | ||||||
| 	// A single indentation level. By default it is two spaces.
 | 	// The string to use for a single indentation level. The default is two
 | ||||||
|  | 	// spaces.
 | ||||||
| 	Indent string | 	Indent string | ||||||
| 
 | 
 | ||||||
| 	// hasWritten is whether we have written any output to w yet.
 | 	// hasWritten is whether we have written any output to w yet.
 | ||||||
|  | @ -51,8 +95,7 @@ type Encoder struct { | ||||||
| 	w          *bufio.Writer | 	w          *bufio.Writer | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewEncoder returns a TOML encoder that encodes Go values to the io.Writer
 | // NewEncoder create a new Encoder.
 | ||||||
| // given. By default, a single indentation level is 2 spaces.
 |  | ||||||
| func NewEncoder(w io.Writer) *Encoder { | func NewEncoder(w io.Writer) *Encoder { | ||||||
| 	return &Encoder{ | 	return &Encoder{ | ||||||
| 		w:      bufio.NewWriter(w), | 		w:      bufio.NewWriter(w), | ||||||
|  | @ -60,29 +103,10 @@ func NewEncoder(w io.Writer) *Encoder { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Encode writes a TOML representation of the Go value to the underlying
 | // Encode writes a TOML representation of the Go value to the Encoder's writer.
 | ||||||
| // 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
 | // An error is returned if the value given cannot be encoded to a valid TOML
 | ||||||
| // as for the Decode* functions. Similarly, the TextMarshaler interface is
 | // document.
 | ||||||
| // 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 { | func (enc *Encoder) Encode(v interface{}) error { | ||||||
| 	rv := eindirect(reflect.ValueOf(v)) | 	rv := eindirect(reflect.ValueOf(v)) | ||||||
| 	if err := enc.safeEncode(Key([]string{}), rv); err != nil { | 	if err := enc.safeEncode(Key([]string{}), rv); err != nil { | ||||||
|  | @ -110,9 +134,13 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) { | ||||||
| 	// Special case. If we can marshal the type to text, then we used that.
 | 	// Special case. If we can marshal the type to text, then we used that.
 | ||||||
| 	// Basically, this prevents the encoder for handling these types as
 | 	// Basically, this prevents the encoder for handling these types as
 | ||||||
| 	// generic structs (or whatever the underlying type of a TextMarshaler is).
 | 	// generic structs (or whatever the underlying type of a TextMarshaler is).
 | ||||||
| 	switch rv.Interface().(type) { | 	switch t := rv.Interface().(type) { | ||||||
| 	case time.Time, TextMarshaler: | 	case time.Time, encoding.TextMarshaler: | ||||||
| 		enc.keyEqElement(key, rv) | 		enc.writeKeyValue(key, rv, false) | ||||||
|  | 		return | ||||||
|  | 	// TODO: #76 would make this superfluous after implemented.
 | ||||||
|  | 	case Primitive: | ||||||
|  | 		enc.encode(key, reflect.ValueOf(t.undecoded)) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -123,12 +151,12 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) { | ||||||
| 		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, | 		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, | ||||||
| 		reflect.Uint64, | 		reflect.Uint64, | ||||||
| 		reflect.Float32, reflect.Float64, reflect.String, reflect.Bool: | 		reflect.Float32, reflect.Float64, reflect.String, reflect.Bool: | ||||||
| 		enc.keyEqElement(key, rv) | 		enc.writeKeyValue(key, rv, false) | ||||||
| 	case reflect.Array, reflect.Slice: | 	case reflect.Array, reflect.Slice: | ||||||
| 		if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) { | 		if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) { | ||||||
| 			enc.eArrayOfTables(key, rv) | 			enc.eArrayOfTables(key, rv) | ||||||
| 		} else { | 		} else { | ||||||
| 			enc.keyEqElement(key, rv) | 			enc.writeKeyValue(key, rv, false) | ||||||
| 		} | 		} | ||||||
| 	case reflect.Interface: | 	case reflect.Interface: | ||||||
| 		if rv.IsNil() { | 		if rv.IsNil() { | ||||||
|  | @ -148,22 +176,32 @@ func (enc *Encoder) encode(key Key, rv reflect.Value) { | ||||||
| 	case reflect.Struct: | 	case reflect.Struct: | ||||||
| 		enc.eTable(key, rv) | 		enc.eTable(key, rv) | ||||||
| 	default: | 	default: | ||||||
| 		panic(e("unsupported type for key '%s': %s", key, k)) | 		encPanic(fmt.Errorf("unsupported type for key '%s': %s", key, k)) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // eElement encodes any value that can be an array element (primitives and
 | // eElement encodes any value that can be an array element.
 | ||||||
| // arrays).
 |  | ||||||
| func (enc *Encoder) eElement(rv reflect.Value) { | func (enc *Encoder) eElement(rv reflect.Value) { | ||||||
| 	switch v := rv.Interface().(type) { | 	switch v := rv.Interface().(type) { | ||||||
| 	case time.Time: | 	case time.Time: // Using TextMarshaler adds extra quotes, which we don't want.
 | ||||||
| 		// Special case time.Time as a primitive. Has to come before
 | 		format := time.RFC3339Nano | ||||||
| 		// TextMarshaler below because time.Time implements
 | 		switch v.Location() { | ||||||
| 		// encoding.TextMarshaler, but we need to always use UTC.
 | 		case internal.LocalDatetime: | ||||||
| 		enc.wf(v.UTC().Format("2006-01-02T15:04:05Z")) | 			format = "2006-01-02T15:04:05.999999999" | ||||||
|  | 		case internal.LocalDate: | ||||||
|  | 			format = "2006-01-02" | ||||||
|  | 		case internal.LocalTime: | ||||||
|  | 			format = "15:04:05.999999999" | ||||||
|  | 		} | ||||||
|  | 		switch v.Location() { | ||||||
|  | 		default: | ||||||
|  | 			enc.wf(v.Format(format)) | ||||||
|  | 		case internal.LocalDatetime, internal.LocalDate, internal.LocalTime: | ||||||
|  | 			enc.wf(v.In(time.UTC).Format(format)) | ||||||
|  | 		} | ||||||
| 		return | 		return | ||||||
| 	case TextMarshaler: | 	case encoding.TextMarshaler: | ||||||
| 		// Special case. Use text marshaler if it's available for this value.
 | 		// Use text marshaler if it's available for this value.
 | ||||||
| 		if s, err := v.MarshalText(); err != nil { | 		if s, err := v.MarshalText(); err != nil { | ||||||
| 			encPanic(err) | 			encPanic(err) | ||||||
| 		} else { | 		} else { | ||||||
|  | @ -171,32 +209,49 @@ func (enc *Encoder) eElement(rv reflect.Value) { | ||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	switch rv.Kind() { | 	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: | 	case reflect.String: | ||||||
| 		enc.writeQuoted(rv.String()) | 		enc.writeQuoted(rv.String()) | ||||||
|  | 	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: | ||||||
|  | 		f := rv.Float() | ||||||
|  | 		if math.IsNaN(f) { | ||||||
|  | 			enc.wf("nan") | ||||||
|  | 		} else if math.IsInf(f, 0) { | ||||||
|  | 			enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)]) | ||||||
|  | 		} else { | ||||||
|  | 			enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32))) | ||||||
|  | 		} | ||||||
|  | 	case reflect.Float64: | ||||||
|  | 		f := rv.Float() | ||||||
|  | 		if math.IsNaN(f) { | ||||||
|  | 			enc.wf("nan") | ||||||
|  | 		} else if math.IsInf(f, 0) { | ||||||
|  | 			enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)]) | ||||||
|  | 		} else { | ||||||
|  | 			enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64))) | ||||||
|  | 		} | ||||||
|  | 	case reflect.Array, reflect.Slice: | ||||||
|  | 		enc.eArrayOrSliceElement(rv) | ||||||
|  | 	case reflect.Struct: | ||||||
|  | 		enc.eStruct(nil, rv, true) | ||||||
|  | 	case reflect.Map: | ||||||
|  | 		enc.eMap(nil, rv, true) | ||||||
|  | 	case reflect.Interface: | ||||||
|  | 		enc.eElement(rv.Elem()) | ||||||
| 	default: | 	default: | ||||||
| 		panic(e("unexpected primitive type: %s", rv.Kind())) | 		encPanic(fmt.Errorf("unexpected primitive type: %T", rv.Interface())) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // By the TOML spec, all floats must have a decimal with at least one
 | // By the TOML spec, all floats must have a decimal with at least one number on
 | ||||||
| // number on either side.
 | // either side.
 | ||||||
| func floatAddDecimal(fstr string) string { | func floatAddDecimal(fstr string) string { | ||||||
| 	if !strings.Contains(fstr, ".") { | 	if !strings.Contains(fstr, ".") { | ||||||
| 		return fstr + ".0" | 		return fstr + ".0" | ||||||
|  | @ -230,16 +285,14 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) { | ||||||
| 		if isNil(trv) { | 		if isNil(trv) { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		panicIfInvalidKey(key) |  | ||||||
| 		enc.newline() | 		enc.newline() | ||||||
| 		enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll()) | 		enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll()) | ||||||
| 		enc.newline() | 		enc.newline() | ||||||
| 		enc.eMapOrStruct(key, trv) | 		enc.eMapOrStruct(key, trv, false) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (enc *Encoder) eTable(key Key, rv reflect.Value) { | func (enc *Encoder) eTable(key Key, rv reflect.Value) { | ||||||
| 	panicIfInvalidKey(key) |  | ||||||
| 	if len(key) == 1 { | 	if len(key) == 1 { | ||||||
| 		// Output an extra newline between top-level tables.
 | 		// Output an extra newline between top-level tables.
 | ||||||
| 		// (The newline isn't written if nothing else has been written though.)
 | 		// (The newline isn't written if nothing else has been written though.)
 | ||||||
|  | @ -249,21 +302,22 @@ func (enc *Encoder) eTable(key Key, rv reflect.Value) { | ||||||
| 		enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll()) | 		enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll()) | ||||||
| 		enc.newline() | 		enc.newline() | ||||||
| 	} | 	} | ||||||
| 	enc.eMapOrStruct(key, rv) | 	enc.eMapOrStruct(key, rv, false) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) { | func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value, inline bool) { | ||||||
| 	switch rv := eindirect(rv); rv.Kind() { | 	switch rv := eindirect(rv); rv.Kind() { | ||||||
| 	case reflect.Map: | 	case reflect.Map: | ||||||
| 		enc.eMap(key, rv) | 		enc.eMap(key, rv, inline) | ||||||
| 	case reflect.Struct: | 	case reflect.Struct: | ||||||
| 		enc.eStruct(key, rv) | 		enc.eStruct(key, rv, inline) | ||||||
| 	default: | 	default: | ||||||
|  | 		// Should never happen?
 | ||||||
| 		panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String()) | 		panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String()) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (enc *Encoder) eMap(key Key, rv reflect.Value) { | func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) { | ||||||
| 	rt := rv.Type() | 	rt := rv.Type() | ||||||
| 	if rt.Key().Kind() != reflect.String { | 	if rt.Key().Kind() != reflect.String { | ||||||
| 		encPanic(errNonString) | 		encPanic(errNonString) | ||||||
|  | @ -281,57 +335,76 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value) { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var writeMapKeys = func(mapKeys []string) { | 	var writeMapKeys = func(mapKeys []string, trailC bool) { | ||||||
| 		sort.Strings(mapKeys) | 		sort.Strings(mapKeys) | ||||||
| 		for _, mapKey := range mapKeys { | 		for i, mapKey := range mapKeys { | ||||||
| 			mrv := rv.MapIndex(reflect.ValueOf(mapKey)) | 			val := rv.MapIndex(reflect.ValueOf(mapKey)) | ||||||
| 			if isNil(mrv) { | 			if isNil(val) { | ||||||
| 				// Don't write anything for nil fields.
 |  | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 			enc.encode(key.add(mapKey), mrv) | 
 | ||||||
|  | 			if inline { | ||||||
|  | 				enc.writeKeyValue(Key{mapKey}, val, true) | ||||||
|  | 				if trailC || i != len(mapKeys)-1 { | ||||||
|  | 					enc.wf(", ") | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				enc.encode(key.add(mapKey), val) | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	writeMapKeys(mapKeysDirect) | 
 | ||||||
| 	writeMapKeys(mapKeysSub) | 	if inline { | ||||||
|  | 		enc.wf("{") | ||||||
|  | 	} | ||||||
|  | 	writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0) | ||||||
|  | 	writeMapKeys(mapKeysSub, false) | ||||||
|  | 	if inline { | ||||||
|  | 		enc.wf("}") | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (enc *Encoder) eStruct(key Key, rv reflect.Value) { | func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) { | ||||||
| 	// Write keys for fields directly under this key first, because if we write
 | 	// 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
 | 	// a field that creates a new table then all keys under it will be in that
 | ||||||
| 	// table (not the one we're writing here).
 | 	// table (not the one we're writing here).
 | ||||||
| 	rt := rv.Type() | 	//
 | ||||||
| 	var fieldsDirect, fieldsSub [][]int | 	// Fields is a [][]int: for fieldsDirect this always has one entry (the
 | ||||||
| 	var addFields func(rt reflect.Type, rv reflect.Value, start []int) | 	// struct index). For fieldsSub it contains two entries: the parent field
 | ||||||
|  | 	// index from tv, and the field indexes for the fields of the sub.
 | ||||||
|  | 	var ( | ||||||
|  | 		rt                      = rv.Type() | ||||||
|  | 		fieldsDirect, fieldsSub [][]int | ||||||
|  | 		addFields               func(rt reflect.Type, rv reflect.Value, start []int) | ||||||
|  | 	) | ||||||
| 	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++ { | 		for i := 0; i < rt.NumField(); i++ { | ||||||
| 			f := rt.Field(i) | 			f := rt.Field(i) | ||||||
| 			// skip unexported fields
 | 			if f.PkgPath != "" && !f.Anonymous { /// Skip unexported fields.
 | ||||||
| 			if f.PkgPath != "" && !f.Anonymous { |  | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
|  | 
 | ||||||
| 			frv := rv.Field(i) | 			frv := rv.Field(i) | ||||||
|  | 
 | ||||||
|  | 			// Treat anonymous struct fields with tag names as though they are
 | ||||||
|  | 			// not anonymous, like encoding/json does.
 | ||||||
|  | 			//
 | ||||||
|  | 			// Non-struct anonymous fields use the normal encoding logic.
 | ||||||
| 			if f.Anonymous { | 			if f.Anonymous { | ||||||
| 				t := f.Type | 				t := f.Type | ||||||
| 				switch t.Kind() { | 				switch t.Kind() { | ||||||
| 				case reflect.Struct: | 				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 == "" { | 					if getOptions(f.Tag).name == "" { | ||||||
| 						addFields(t, frv, f.Index) | 						addFields(t, frv, append(start, f.Index...)) | ||||||
| 						continue | 						continue | ||||||
| 					} | 					} | ||||||
| 				case reflect.Ptr: | 				case reflect.Ptr: | ||||||
| 					if t.Elem().Kind() == reflect.Struct && | 					if t.Elem().Kind() == reflect.Struct && getOptions(f.Tag).name == "" { | ||||||
| 						getOptions(f.Tag).name == "" { |  | ||||||
| 						if !frv.IsNil() { | 						if !frv.IsNil() { | ||||||
| 							addFields(t.Elem(), frv.Elem(), f.Index) | 							addFields(t.Elem(), frv.Elem(), append(start, f.Index...)) | ||||||
| 						} | 						} | ||||||
| 						continue | 						continue | ||||||
| 					} | 					} | ||||||
| 					// Fall through to the normal field encoding logic below
 |  | ||||||
| 					// for non-struct anonymous fields.
 |  | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | @ -344,35 +417,49 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value) { | ||||||
| 	} | 	} | ||||||
| 	addFields(rt, rv, nil) | 	addFields(rt, rv, nil) | ||||||
| 
 | 
 | ||||||
| 	var writeFields = func(fields [][]int) { | 	writeFields := func(fields [][]int) { | ||||||
| 		for _, fieldIndex := range fields { | 		for _, fieldIndex := range fields { | ||||||
| 			sft := rt.FieldByIndex(fieldIndex) | 			fieldType := rt.FieldByIndex(fieldIndex) | ||||||
| 			sf := rv.FieldByIndex(fieldIndex) | 			fieldVal := rv.FieldByIndex(fieldIndex) | ||||||
| 			if isNil(sf) { | 
 | ||||||
| 				// Don't write anything for nil fields.
 | 			if isNil(fieldVal) { /// Don't write anything for nil fields.
 | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			opts := getOptions(sft.Tag) | 			opts := getOptions(fieldType.Tag) | ||||||
| 			if opts.skip { | 			if opts.skip { | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 			keyName := sft.Name | 			keyName := fieldType.Name | ||||||
| 			if opts.name != "" { | 			if opts.name != "" { | ||||||
| 				keyName = opts.name | 				keyName = opts.name | ||||||
| 			} | 			} | ||||||
| 			if opts.omitempty && isEmpty(sf) { | 			if opts.omitempty && isEmpty(fieldVal) { | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 			if opts.omitzero && isZero(sf) { | 			if opts.omitzero && isZero(fieldVal) { | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			enc.encode(key.add(keyName), sf) | 			if inline { | ||||||
|  | 				enc.writeKeyValue(Key{keyName}, fieldVal, true) | ||||||
|  | 				if fieldIndex[0] != len(fields)-1 { | ||||||
|  | 					enc.wf(", ") | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				enc.encode(key.add(keyName), fieldVal) | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if inline { | ||||||
|  | 		enc.wf("{") | ||||||
|  | 	} | ||||||
| 	writeFields(fieldsDirect) | 	writeFields(fieldsDirect) | ||||||
| 	writeFields(fieldsSub) | 	writeFields(fieldsSub) | ||||||
|  | 	if inline { | ||||||
|  | 		enc.wf("}") | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // tomlTypeName returns the TOML type name of the Go value's type. It is
 | // tomlTypeName returns the TOML type name of the Go value's type. It is
 | ||||||
|  | @ -411,13 +498,26 @@ func tomlTypeOfGo(rv reflect.Value) tomlType { | ||||||
| 		switch rv.Interface().(type) { | 		switch rv.Interface().(type) { | ||||||
| 		case time.Time: | 		case time.Time: | ||||||
| 			return tomlDatetime | 			return tomlDatetime | ||||||
| 		case TextMarshaler: | 		case encoding.TextMarshaler: | ||||||
| 			return tomlString | 			return tomlString | ||||||
| 		default: | 		default: | ||||||
|  | 			// Someone used a pointer receiver: we can make it work for pointer
 | ||||||
|  | 			// values.
 | ||||||
|  | 			if rv.CanAddr() { | ||||||
|  | 				_, ok := rv.Addr().Interface().(encoding.TextMarshaler) | ||||||
|  | 				if ok { | ||||||
|  | 					return tomlString | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 			return tomlHash | 			return tomlHash | ||||||
| 		} | 		} | ||||||
| 	default: | 	default: | ||||||
| 		panic("unexpected reflect.Kind: " + rv.Kind().String()) | 		_, ok := rv.Interface().(encoding.TextMarshaler) | ||||||
|  | 		if ok { | ||||||
|  | 			return tomlString | ||||||
|  | 		} | ||||||
|  | 		encPanic(errors.New("unsupported type: " + rv.Kind().String())) | ||||||
|  | 		panic("") // Need *some* return value
 | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -430,30 +530,19 @@ func tomlArrayType(rv reflect.Value) tomlType { | ||||||
| 	if isNil(rv) || !rv.IsValid() || rv.Len() == 0 { | 	if isNil(rv) || !rv.IsValid() || rv.Len() == 0 { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	/// Don't allow nil.
 | ||||||
|  | 	rvlen := rv.Len() | ||||||
|  | 	for i := 1; i < rvlen; i++ { | ||||||
|  | 		if tomlTypeOfGo(rv.Index(i)) == nil { | ||||||
|  | 			encPanic(errArrayNilElement) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	firstType := tomlTypeOfGo(rv.Index(0)) | 	firstType := tomlTypeOfGo(rv.Index(0)) | ||||||
| 	if firstType == nil { | 	if firstType == nil { | ||||||
| 		encPanic(errArrayNilElement) | 		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 | 	return firstType | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -511,14 +600,20 @@ func (enc *Encoder) newline() { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (enc *Encoder) keyEqElement(key Key, val reflect.Value) { | // Write a key/value pair:
 | ||||||
|  | //
 | ||||||
|  | //   key = <any value>
 | ||||||
|  | //
 | ||||||
|  | // If inline is true it won't add a newline at the end.
 | ||||||
|  | func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) { | ||||||
| 	if len(key) == 0 { | 	if len(key) == 0 { | ||||||
| 		encPanic(errNoKey) | 		encPanic(errNoKey) | ||||||
| 	} | 	} | ||||||
| 	panicIfInvalidKey(key) |  | ||||||
| 	enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) | 	enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) | ||||||
| 	enc.eElement(val) | 	enc.eElement(val) | ||||||
| 	enc.newline() | 	if !inline { | ||||||
|  | 		enc.newline() | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (enc *Encoder) wf(format string, v ...interface{}) { | func (enc *Encoder) wf(format string, v ...interface{}) { | ||||||
|  | @ -553,16 +648,3 @@ func isNil(rv reflect.Value) bool { | ||||||
| 		return false | 		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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -1,19 +0,0 @@ | ||||||
| // +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 |  | ||||||
|  | @ -1,18 +0,0 @@ | ||||||
| // +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,3 @@ | ||||||
|  | module github.com/BurntSushi/toml | ||||||
|  | 
 | ||||||
|  | go 1.16 | ||||||
|  | @ -0,0 +1,36 @@ | ||||||
|  | package internal | ||||||
|  | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
|  | // Timezones used for local datetime, date, and time TOML types.
 | ||||||
|  | //
 | ||||||
|  | // The exact way times and dates without a timezone should be interpreted is not
 | ||||||
|  | // well-defined in the TOML specification and left to the implementation. These
 | ||||||
|  | // defaults to current local timezone offset of the computer, but this can be
 | ||||||
|  | // changed by changing these variables before decoding.
 | ||||||
|  | //
 | ||||||
|  | // TODO:
 | ||||||
|  | // Ideally we'd like to offer people the ability to configure the used timezone
 | ||||||
|  | // by setting Decoder.Timezone and Encoder.Timezone; however, this is a bit
 | ||||||
|  | // tricky: the reason we use three different variables for this is to support
 | ||||||
|  | // round-tripping – without these specific TZ names we wouldn't know which
 | ||||||
|  | // format to use.
 | ||||||
|  | //
 | ||||||
|  | // There isn't a good way to encode this right now though, and passing this sort
 | ||||||
|  | // of information also ties in to various related issues such as string format
 | ||||||
|  | // encoding, encoding of comments, etc.
 | ||||||
|  | //
 | ||||||
|  | // So, for the time being, just put this in internal until we can write a good
 | ||||||
|  | // comprehensive API for doing all of this.
 | ||||||
|  | //
 | ||||||
|  | // The reason they're exported is because they're referred from in e.g.
 | ||||||
|  | // internal/tag.
 | ||||||
|  | //
 | ||||||
|  | // Note that this behaviour is valid according to the TOML spec as the exact
 | ||||||
|  | // behaviour is left up to implementations.
 | ||||||
|  | var ( | ||||||
|  | 	localOffset   = func() int { _, o := time.Now().Zone(); return o }() | ||||||
|  | 	LocalDatetime = time.FixedZone("datetime-local", localOffset) | ||||||
|  | 	LocalDate     = time.FixedZone("date-local", localOffset) | ||||||
|  | 	LocalTime     = time.FixedZone("time-local", localOffset) | ||||||
|  | ) | ||||||
|  | @ -2,6 +2,8 @@ package toml | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"reflect" | ||||||
|  | 	"runtime" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"unicode" | 	"unicode" | ||||||
| 	"unicode/utf8" | 	"unicode/utf8" | ||||||
|  | @ -29,6 +31,7 @@ const ( | ||||||
| 	itemArrayTableStart | 	itemArrayTableStart | ||||||
| 	itemArrayTableEnd | 	itemArrayTableEnd | ||||||
| 	itemKeyStart | 	itemKeyStart | ||||||
|  | 	itemKeyEnd | ||||||
| 	itemCommentStart | 	itemCommentStart | ||||||
| 	itemInlineTableStart | 	itemInlineTableStart | ||||||
| 	itemInlineTableEnd | 	itemInlineTableEnd | ||||||
|  | @ -64,9 +67,9 @@ type lexer struct { | ||||||
| 	state stateFn | 	state stateFn | ||||||
| 	items chan item | 	items chan item | ||||||
| 
 | 
 | ||||||
| 	// Allow for backing up up to three runes.
 | 	// Allow for backing up up to four runes.
 | ||||||
| 	// This is necessary because TOML contains 3-rune tokens (""" and ''').
 | 	// This is necessary because TOML contains 3-rune tokens (""" and ''').
 | ||||||
| 	prevWidths [3]int | 	prevWidths [4]int | ||||||
| 	nprev      int // how many of prevWidths are in use
 | 	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
 | 	// If we emit an eof, we can still back up, but it is not OK to call
 | ||||||
| 	// next again.
 | 	// next again.
 | ||||||
|  | @ -93,6 +96,7 @@ func (lx *lexer) nextItem() item { | ||||||
| 			return item | 			return item | ||||||
| 		default: | 		default: | ||||||
| 			lx.state = lx.state(lx) | 			lx.state = lx.state(lx) | ||||||
|  | 			//fmt.Printf("     STATE %-24s   current: %-10q   stack: %s\n", lx.state, lx.current(), lx.stack)
 | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | @ -137,7 +141,7 @@ func (lx *lexer) emitTrim(typ itemType) { | ||||||
| 
 | 
 | ||||||
| func (lx *lexer) next() (r rune) { | func (lx *lexer) next() (r rune) { | ||||||
| 	if lx.atEOF { | 	if lx.atEOF { | ||||||
| 		panic("next called after EOF") | 		panic("BUG in lexer: next called after EOF") | ||||||
| 	} | 	} | ||||||
| 	if lx.pos >= len(lx.input) { | 	if lx.pos >= len(lx.input) { | ||||||
| 		lx.atEOF = true | 		lx.atEOF = true | ||||||
|  | @ -147,12 +151,19 @@ func (lx *lexer) next() (r rune) { | ||||||
| 	if lx.input[lx.pos] == '\n' { | 	if lx.input[lx.pos] == '\n' { | ||||||
| 		lx.line++ | 		lx.line++ | ||||||
| 	} | 	} | ||||||
|  | 	lx.prevWidths[3] = lx.prevWidths[2] | ||||||
| 	lx.prevWidths[2] = lx.prevWidths[1] | 	lx.prevWidths[2] = lx.prevWidths[1] | ||||||
| 	lx.prevWidths[1] = lx.prevWidths[0] | 	lx.prevWidths[1] = lx.prevWidths[0] | ||||||
| 	if lx.nprev < 3 { | 	if lx.nprev < 4 { | ||||||
| 		lx.nprev++ | 		lx.nprev++ | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	r, w := utf8.DecodeRuneInString(lx.input[lx.pos:]) | 	r, w := utf8.DecodeRuneInString(lx.input[lx.pos:]) | ||||||
|  | 	if r == utf8.RuneError { | ||||||
|  | 		lx.errorf("invalid UTF-8 byte at position %d (line %d): 0x%02x", lx.pos, lx.line, lx.input[lx.pos]) | ||||||
|  | 		return utf8.RuneError | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	lx.prevWidths[0] = w | 	lx.prevWidths[0] = w | ||||||
| 	lx.pos += w | 	lx.pos += w | ||||||
| 	return r | 	return r | ||||||
|  | @ -163,18 +174,19 @@ func (lx *lexer) ignore() { | ||||||
| 	lx.start = lx.pos | 	lx.start = lx.pos | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // backup steps back one rune. Can be called only twice between calls to next.
 | // backup steps back one rune. Can be called 4 times between calls to next.
 | ||||||
| func (lx *lexer) backup() { | func (lx *lexer) backup() { | ||||||
| 	if lx.atEOF { | 	if lx.atEOF { | ||||||
| 		lx.atEOF = false | 		lx.atEOF = false | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if lx.nprev < 1 { | 	if lx.nprev < 1 { | ||||||
| 		panic("backed up too far") | 		panic("BUG in lexer: backed up too far") | ||||||
| 	} | 	} | ||||||
| 	w := lx.prevWidths[0] | 	w := lx.prevWidths[0] | ||||||
| 	lx.prevWidths[0] = lx.prevWidths[1] | 	lx.prevWidths[0] = lx.prevWidths[1] | ||||||
| 	lx.prevWidths[1] = lx.prevWidths[2] | 	lx.prevWidths[1] = lx.prevWidths[2] | ||||||
|  | 	lx.prevWidths[2] = lx.prevWidths[3] | ||||||
| 	lx.nprev-- | 	lx.nprev-- | ||||||
| 	lx.pos -= w | 	lx.pos -= w | ||||||
| 	if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { | 	if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { | ||||||
|  | @ -269,8 +281,9 @@ func lexTopEnd(lx *lexer) stateFn { | ||||||
| 		lx.emit(itemEOF) | 		lx.emit(itemEOF) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	return lx.errorf("expected a top-level item to end with a newline, "+ | 	return lx.errorf( | ||||||
| 		"comment, or EOF, but got %q instead", r) | 		"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
 | // lexTable lexes the beginning of a table. Namely, it makes sure that
 | ||||||
|  | @ -297,8 +310,9 @@ func lexTableEnd(lx *lexer) stateFn { | ||||||
| 
 | 
 | ||||||
| func lexArrayTableEnd(lx *lexer) stateFn { | func lexArrayTableEnd(lx *lexer) stateFn { | ||||||
| 	if r := lx.next(); r != arrayTableEnd { | 	if r := lx.next(); r != arrayTableEnd { | ||||||
| 		return lx.errorf("expected end of table array name delimiter %q, "+ | 		return lx.errorf( | ||||||
| 			"but got %q instead", arrayTableEnd, r) | 			"expected end of table array name delimiter %q, but got %q instead", | ||||||
|  | 			arrayTableEnd, r) | ||||||
| 	} | 	} | ||||||
| 	lx.emit(itemArrayTableEnd) | 	lx.emit(itemArrayTableEnd) | ||||||
| 	return lexTopEnd | 	return lexTopEnd | ||||||
|  | @ -308,32 +322,19 @@ func lexTableNameStart(lx *lexer) stateFn { | ||||||
| 	lx.skip(isWhitespace) | 	lx.skip(isWhitespace) | ||||||
| 	switch r := lx.peek(); { | 	switch r := lx.peek(); { | ||||||
| 	case r == tableEnd || r == eof: | 	case r == tableEnd || r == eof: | ||||||
| 		return lx.errorf("unexpected end of table name " + | 		return lx.errorf("unexpected end of table name (table names cannot be empty)") | ||||||
| 			"(table names cannot be empty)") |  | ||||||
| 	case r == tableSep: | 	case r == tableSep: | ||||||
| 		return lx.errorf("unexpected table separator " + | 		return lx.errorf("unexpected table separator (table names cannot be empty)") | ||||||
| 			"(table names cannot be empty)") |  | ||||||
| 	case r == stringStart || r == rawStringStart: | 	case r == stringStart || r == rawStringStart: | ||||||
| 		lx.ignore() | 		lx.ignore() | ||||||
| 		lx.push(lexTableNameEnd) | 		lx.push(lexTableNameEnd) | ||||||
| 		return lexValue // reuse string lexing
 | 		return lexQuotedName | ||||||
| 	default: | 	default: | ||||||
| 		return lexBareTableName | 		lx.push(lexTableNameEnd) | ||||||
|  | 		return lexBareName | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 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
 | // lexTableNameEnd reads the end of a piece of a table name, optionally
 | ||||||
| // consuming whitespace.
 | // consuming whitespace.
 | ||||||
| func lexTableNameEnd(lx *lexer) stateFn { | func lexTableNameEnd(lx *lexer) stateFn { | ||||||
|  | @ -347,63 +348,101 @@ func lexTableNameEnd(lx *lexer) stateFn { | ||||||
| 	case r == tableEnd: | 	case r == tableEnd: | ||||||
| 		return lx.pop() | 		return lx.pop() | ||||||
| 	default: | 	default: | ||||||
| 		return lx.errorf("expected '.' or ']' to end table name, "+ | 		return lx.errorf("expected '.' or ']' to end table name, but got %q instead", r) | ||||||
| 			"but got %q instead", r) |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // lexKeyStart consumes a key name up until the first non-whitespace character.
 | // lexBareName lexes one part of a key or table.
 | ||||||
| // lexKeyStart will ignore whitespace.
 | //
 | ||||||
| func lexKeyStart(lx *lexer) stateFn { | // It assumes that at least one valid character for the table has already been
 | ||||||
| 	r := lx.peek() | // read.
 | ||||||
|  | //
 | ||||||
|  | // Lexes only one part, e.g. only 'a' inside 'a.b'.
 | ||||||
|  | func lexBareName(lx *lexer) stateFn { | ||||||
|  | 	r := lx.next() | ||||||
|  | 	if isBareKeyChar(r) { | ||||||
|  | 		return lexBareName | ||||||
|  | 	} | ||||||
|  | 	lx.backup() | ||||||
|  | 	lx.emit(itemText) | ||||||
|  | 	return lx.pop() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // lexBareName lexes one part of a key or table.
 | ||||||
|  | //
 | ||||||
|  | // It assumes that at least one valid character for the table has already been
 | ||||||
|  | // read.
 | ||||||
|  | //
 | ||||||
|  | // Lexes only one part, e.g. only '"a"' inside '"a".b'.
 | ||||||
|  | func lexQuotedName(lx *lexer) stateFn { | ||||||
|  | 	r := lx.next() | ||||||
| 	switch { | 	switch { | ||||||
| 	case r == keySep: | 	case isWhitespace(r): | ||||||
| 		return lx.errorf("unexpected key separator %q", keySep) | 		return lexSkip(lx, lexValue) | ||||||
| 	case isWhitespace(r) || isNL(r): | 	case r == stringStart: | ||||||
| 		lx.next() | 		lx.ignore() // ignore the '"'
 | ||||||
| 		return lexSkip(lx, lexKeyStart) | 		return lexString | ||||||
|  | 	case r == rawStringStart: | ||||||
|  | 		lx.ignore() // ignore the "'"
 | ||||||
|  | 		return lexRawString | ||||||
|  | 	case r == eof: | ||||||
|  | 		return lx.errorf("unexpected EOF; expected value") | ||||||
|  | 	default: | ||||||
|  | 		return lx.errorf("expected value but found %q instead", r) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // lexKeyStart consumes all key parts until a '='.
 | ||||||
|  | func lexKeyStart(lx *lexer) stateFn { | ||||||
|  | 	lx.skip(isWhitespace) | ||||||
|  | 	switch r := lx.peek(); { | ||||||
|  | 	case r == '=' || r == eof: | ||||||
|  | 		return lx.errorf("unexpected '=': key name appears blank") | ||||||
|  | 	case r == '.': | ||||||
|  | 		return lx.errorf("unexpected '.': keys cannot start with a '.'") | ||||||
| 	case r == stringStart || r == rawStringStart: | 	case r == stringStart || r == rawStringStart: | ||||||
| 		lx.ignore() | 		lx.ignore() | ||||||
|  | 		fallthrough | ||||||
|  | 	default: // Bare key
 | ||||||
| 		lx.emit(itemKeyStart) | 		lx.emit(itemKeyStart) | ||||||
| 		lx.push(lexKeyEnd) | 		return lexKeyNameStart | ||||||
| 		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
 | func lexKeyNameStart(lx *lexer) stateFn { | ||||||
| // (which is not whitespace) has not yet been consumed.
 | 	lx.skip(isWhitespace) | ||||||
| func lexBareKey(lx *lexer) stateFn { | 	switch r := lx.peek(); { | ||||||
| 	switch r := lx.next(); { | 	case r == '=' || r == eof: | ||||||
| 	case isBareKeyChar(r): | 		return lx.errorf("unexpected '='") | ||||||
| 		return lexBareKey | 	case r == '.': | ||||||
| 	case isWhitespace(r): | 		return lx.errorf("unexpected '.'") | ||||||
| 		lx.backup() | 	case r == stringStart || r == rawStringStart: | ||||||
| 		lx.emit(itemText) | 		lx.ignore() | ||||||
| 		return lexKeyEnd | 		lx.push(lexKeyEnd) | ||||||
| 	case r == keySep: | 		return lexQuotedName | ||||||
| 		lx.backup() |  | ||||||
| 		lx.emit(itemText) |  | ||||||
| 		return lexKeyEnd |  | ||||||
| 	default: | 	default: | ||||||
| 		return lx.errorf("bare keys cannot contain %q", r) | 		lx.push(lexKeyEnd) | ||||||
|  | 		return lexBareName | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // lexKeyEnd consumes the end of a key and trims whitespace (up to the key
 | // lexKeyEnd consumes the end of a key and trims whitespace (up to the key
 | ||||||
| // separator).
 | // separator).
 | ||||||
| func lexKeyEnd(lx *lexer) stateFn { | func lexKeyEnd(lx *lexer) stateFn { | ||||||
|  | 	lx.skip(isWhitespace) | ||||||
| 	switch r := lx.next(); { | 	switch r := lx.next(); { | ||||||
| 	case r == keySep: |  | ||||||
| 		return lexSkip(lx, lexValue) |  | ||||||
| 	case isWhitespace(r): | 	case isWhitespace(r): | ||||||
| 		return lexSkip(lx, lexKeyEnd) | 		return lexSkip(lx, lexKeyEnd) | ||||||
|  | 	case r == eof: | ||||||
|  | 		return lx.errorf("unexpected EOF; expected key separator %q", keySep) | ||||||
|  | 	case r == '.': | ||||||
|  | 		lx.ignore() | ||||||
|  | 		return lexKeyNameStart | ||||||
|  | 	case r == '=': | ||||||
|  | 		lx.emit(itemKeyEnd) | ||||||
|  | 		return lexSkip(lx, lexValue) | ||||||
| 	default: | 	default: | ||||||
| 		return lx.errorf("expected key separator %q, but got %q instead", | 		return lx.errorf("expected '.' or '=', but got %q instead", r) | ||||||
| 			keySep, r) |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -450,10 +489,15 @@ func lexValue(lx *lexer) stateFn { | ||||||
| 		} | 		} | ||||||
| 		lx.ignore() // ignore the "'"
 | 		lx.ignore() // ignore the "'"
 | ||||||
| 		return lexRawString | 		return lexRawString | ||||||
| 	case '+', '-': |  | ||||||
| 		return lexNumberStart |  | ||||||
| 	case '.': // special error case, be kind to users
 | 	case '.': // special error case, be kind to users
 | ||||||
| 		return lx.errorf("floats must start with a digit, not '.'") | 		return lx.errorf("floats must start with a digit, not '.'") | ||||||
|  | 	case 'i', 'n': | ||||||
|  | 		if (lx.accept('n') && lx.accept('f')) || (lx.accept('a') && lx.accept('n')) { | ||||||
|  | 			lx.emit(itemFloat) | ||||||
|  | 			return lx.pop() | ||||||
|  | 		} | ||||||
|  | 	case '-', '+': | ||||||
|  | 		return lexDecimalNumberStart | ||||||
| 	} | 	} | ||||||
| 	if unicode.IsLetter(r) { | 	if unicode.IsLetter(r) { | ||||||
| 		// Be permissive here; lexBool will give a nice error if the
 | 		// Be permissive here; lexBool will give a nice error if the
 | ||||||
|  | @ -463,6 +507,9 @@ func lexValue(lx *lexer) stateFn { | ||||||
| 		lx.backup() | 		lx.backup() | ||||||
| 		return lexBool | 		return lexBool | ||||||
| 	} | 	} | ||||||
|  | 	if r == eof { | ||||||
|  | 		return lx.errorf("unexpected EOF; expected value") | ||||||
|  | 	} | ||||||
| 	return lx.errorf("expected value but found %q instead", r) | 	return lx.errorf("expected value but found %q instead", r) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -507,9 +554,8 @@ func lexArrayValueEnd(lx *lexer) stateFn { | ||||||
| 		return lexArrayEnd | 		return lexArrayEnd | ||||||
| 	} | 	} | ||||||
| 	return lx.errorf( | 	return lx.errorf( | ||||||
| 		"expected a comma or array terminator %q, but got %q instead", | 		"expected a comma or array terminator %q, but got %s instead", | ||||||
| 		arrayEnd, r, | 		arrayEnd, runeOrEOF(r)) | ||||||
| 	) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // lexArrayEnd finishes the lexing of an array.
 | // lexArrayEnd finishes the lexing of an array.
 | ||||||
|  | @ -546,8 +592,7 @@ func lexInlineTableValue(lx *lexer) stateFn { | ||||||
| // key/value pair and the next pair (or the end of the table):
 | // key/value pair and the next pair (or the end of the table):
 | ||||||
| // it ignores whitespace and expects either a ',' or a '}'.
 | // it ignores whitespace and expects either a ',' or a '}'.
 | ||||||
| func lexInlineTableValueEnd(lx *lexer) stateFn { | func lexInlineTableValueEnd(lx *lexer) stateFn { | ||||||
| 	r := lx.next() | 	switch r := lx.next(); { | ||||||
| 	switch { |  | ||||||
| 	case isWhitespace(r): | 	case isWhitespace(r): | ||||||
| 		return lexSkip(lx, lexInlineTableValueEnd) | 		return lexSkip(lx, lexInlineTableValueEnd) | ||||||
| 	case isNL(r): | 	case isNL(r): | ||||||
|  | @ -557,12 +602,25 @@ func lexInlineTableValueEnd(lx *lexer) stateFn { | ||||||
| 		return lexCommentStart | 		return lexCommentStart | ||||||
| 	case r == comma: | 	case r == comma: | ||||||
| 		lx.ignore() | 		lx.ignore() | ||||||
|  | 		lx.skip(isWhitespace) | ||||||
|  | 		if lx.peek() == '}' { | ||||||
|  | 			return lx.errorf("trailing comma not allowed in inline tables") | ||||||
|  | 		} | ||||||
| 		return lexInlineTableValue | 		return lexInlineTableValue | ||||||
| 	case r == inlineTableEnd: | 	case r == inlineTableEnd: | ||||||
| 		return lexInlineTableEnd | 		return lexInlineTableEnd | ||||||
|  | 	default: | ||||||
|  | 		return lx.errorf( | ||||||
|  | 			"expected a comma or an inline table terminator %q, but got %s instead", | ||||||
|  | 			inlineTableEnd, runeOrEOF(r)) | ||||||
| 	} | 	} | ||||||
| 	return lx.errorf("expected a comma or an inline table terminator %q, "+ | } | ||||||
| 		"but got %q instead", inlineTableEnd, r) | 
 | ||||||
|  | func runeOrEOF(r rune) string { | ||||||
|  | 	if r == eof { | ||||||
|  | 		return "end of file" | ||||||
|  | 	} | ||||||
|  | 	return "'" + string(r) + "'" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // lexInlineTableEnd finishes the lexing of an inline table.
 | // lexInlineTableEnd finishes the lexing of an inline table.
 | ||||||
|  | @ -579,7 +637,9 @@ func lexString(lx *lexer) stateFn { | ||||||
| 	r := lx.next() | 	r := lx.next() | ||||||
| 	switch { | 	switch { | ||||||
| 	case r == eof: | 	case r == eof: | ||||||
| 		return lx.errorf("unexpected EOF") | 		return lx.errorf(`unexpected EOF; expected '"'`) | ||||||
|  | 	case isControl(r) || r == '\r': | ||||||
|  | 		return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r) | ||||||
| 	case isNL(r): | 	case isNL(r): | ||||||
| 		return lx.errorf("strings cannot contain newlines") | 		return lx.errorf("strings cannot contain newlines") | ||||||
| 	case r == '\\': | 	case r == '\\': | ||||||
|  | @ -598,19 +658,40 @@ func lexString(lx *lexer) stateFn { | ||||||
| // lexMultilineString consumes the inner contents of a string. It assumes that
 | // lexMultilineString consumes the inner contents of a string. It assumes that
 | ||||||
| // the beginning '"""' has already been consumed and ignored.
 | // the beginning '"""' has already been consumed and ignored.
 | ||||||
| func lexMultilineString(lx *lexer) stateFn { | func lexMultilineString(lx *lexer) stateFn { | ||||||
| 	switch lx.next() { | 	r := lx.next() | ||||||
|  | 	switch r { | ||||||
| 	case eof: | 	case eof: | ||||||
| 		return lx.errorf("unexpected EOF") | 		return lx.errorf(`unexpected EOF; expected '"""'`) | ||||||
|  | 	case '\r': | ||||||
|  | 		if lx.peek() != '\n' { | ||||||
|  | 			return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r) | ||||||
|  | 		} | ||||||
|  | 		return lexMultilineString | ||||||
| 	case '\\': | 	case '\\': | ||||||
| 		return lexMultilineStringEscape | 		return lexMultilineStringEscape | ||||||
| 	case stringEnd: | 	case stringEnd: | ||||||
|  | 		/// Found " → try to read two more "".
 | ||||||
| 		if lx.accept(stringEnd) { | 		if lx.accept(stringEnd) { | ||||||
| 			if lx.accept(stringEnd) { | 			if lx.accept(stringEnd) { | ||||||
| 				lx.backup() | 				/// Peek ahead: the string can contain " and "", including at the
 | ||||||
|  | 				/// end: """str"""""
 | ||||||
|  | 				/// 6 or more at the end, however, is an error.
 | ||||||
|  | 				if lx.peek() == stringEnd { | ||||||
|  | 					/// Check if we already lexed 5 's; if so we have 6 now, and
 | ||||||
|  | 					/// that's just too many man!
 | ||||||
|  | 					if strings.HasSuffix(lx.current(), `"""""`) { | ||||||
|  | 						return lx.errorf(`unexpected '""""""'`) | ||||||
|  | 					} | ||||||
|  | 					lx.backup() | ||||||
|  | 					lx.backup() | ||||||
|  | 					return lexMultilineString | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				lx.backup() /// backup: don't include the """ in the item.
 | ||||||
| 				lx.backup() | 				lx.backup() | ||||||
| 				lx.backup() | 				lx.backup() | ||||||
| 				lx.emit(itemMultilineString) | 				lx.emit(itemMultilineString) | ||||||
| 				lx.next() | 				lx.next() /// Read over ''' again and discard it.
 | ||||||
| 				lx.next() | 				lx.next() | ||||||
| 				lx.next() | 				lx.next() | ||||||
| 				lx.ignore() | 				lx.ignore() | ||||||
|  | @ -619,6 +700,10 @@ func lexMultilineString(lx *lexer) stateFn { | ||||||
| 			lx.backup() | 			lx.backup() | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if isControl(r) { | ||||||
|  | 		return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r) | ||||||
|  | 	} | ||||||
| 	return lexMultilineString | 	return lexMultilineString | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -628,7 +713,9 @@ func lexRawString(lx *lexer) stateFn { | ||||||
| 	r := lx.next() | 	r := lx.next() | ||||||
| 	switch { | 	switch { | ||||||
| 	case r == eof: | 	case r == eof: | ||||||
| 		return lx.errorf("unexpected EOF") | 		return lx.errorf(`unexpected EOF; expected "'"`) | ||||||
|  | 	case isControl(r) || r == '\r': | ||||||
|  | 		return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r) | ||||||
| 	case isNL(r): | 	case isNL(r): | ||||||
| 		return lx.errorf("strings cannot contain newlines") | 		return lx.errorf("strings cannot contain newlines") | ||||||
| 	case r == rawStringEnd: | 	case r == rawStringEnd: | ||||||
|  | @ -645,17 +732,38 @@ func lexRawString(lx *lexer) stateFn { | ||||||
| // a string. It assumes that the beginning "'''" has already been consumed and
 | // a string. It assumes that the beginning "'''" has already been consumed and
 | ||||||
| // ignored.
 | // ignored.
 | ||||||
| func lexMultilineRawString(lx *lexer) stateFn { | func lexMultilineRawString(lx *lexer) stateFn { | ||||||
| 	switch lx.next() { | 	r := lx.next() | ||||||
|  | 	switch r { | ||||||
| 	case eof: | 	case eof: | ||||||
| 		return lx.errorf("unexpected EOF") | 		return lx.errorf(`unexpected EOF; expected "'''"`) | ||||||
|  | 	case '\r': | ||||||
|  | 		if lx.peek() != '\n' { | ||||||
|  | 			return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r) | ||||||
|  | 		} | ||||||
|  | 		return lexMultilineRawString | ||||||
| 	case rawStringEnd: | 	case rawStringEnd: | ||||||
|  | 		/// Found ' → try to read two more ''.
 | ||||||
| 		if lx.accept(rawStringEnd) { | 		if lx.accept(rawStringEnd) { | ||||||
| 			if lx.accept(rawStringEnd) { | 			if lx.accept(rawStringEnd) { | ||||||
| 				lx.backup() | 				/// Peek ahead: the string can contain ' and '', including at the
 | ||||||
|  | 				/// end: '''str'''''
 | ||||||
|  | 				/// 6 or more at the end, however, is an error.
 | ||||||
|  | 				if lx.peek() == rawStringEnd { | ||||||
|  | 					/// Check if we already lexed 5 's; if so we have 6 now, and
 | ||||||
|  | 					/// that's just too many man!
 | ||||||
|  | 					if strings.HasSuffix(lx.current(), "'''''") { | ||||||
|  | 						return lx.errorf(`unexpected "''''''"`) | ||||||
|  | 					} | ||||||
|  | 					lx.backup() | ||||||
|  | 					lx.backup() | ||||||
|  | 					return lexMultilineRawString | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				lx.backup() /// backup: don't include the ''' in the item.
 | ||||||
| 				lx.backup() | 				lx.backup() | ||||||
| 				lx.backup() | 				lx.backup() | ||||||
| 				lx.emit(itemRawMultilineString) | 				lx.emit(itemRawMultilineString) | ||||||
| 				lx.next() | 				lx.next() /// Read over ''' again and discard it.
 | ||||||
| 				lx.next() | 				lx.next() | ||||||
| 				lx.next() | 				lx.next() | ||||||
| 				lx.ignore() | 				lx.ignore() | ||||||
|  | @ -664,6 +772,10 @@ func lexMultilineRawString(lx *lexer) stateFn { | ||||||
| 			lx.backup() | 			lx.backup() | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if isControl(r) { | ||||||
|  | 		return lx.errorf("control characters are not allowed inside strings: '0x%02x'", r) | ||||||
|  | 	} | ||||||
| 	return lexMultilineRawString | 	return lexMultilineRawString | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -694,6 +806,10 @@ func lexStringEscape(lx *lexer) stateFn { | ||||||
| 		fallthrough | 		fallthrough | ||||||
| 	case '"': | 	case '"': | ||||||
| 		fallthrough | 		fallthrough | ||||||
|  | 	case ' ', '\t': | ||||||
|  | 		// Inside """ .. """ strings you can use \ to escape newlines, and any
 | ||||||
|  | 		// amount of whitespace can be between the \ and \n.
 | ||||||
|  | 		fallthrough | ||||||
| 	case '\\': | 	case '\\': | ||||||
| 		return lx.pop() | 		return lx.pop() | ||||||
| 	case 'u': | 	case 'u': | ||||||
|  | @ -701,8 +817,7 @@ func lexStringEscape(lx *lexer) stateFn { | ||||||
| 	case 'U': | 	case 'U': | ||||||
| 		return lexLongUnicodeEscape | 		return lexLongUnicodeEscape | ||||||
| 	} | 	} | ||||||
| 	return lx.errorf("invalid escape character %q; only the following "+ | 	return lx.errorf("invalid escape character %q; only the following escape characters are allowed: "+ | ||||||
| 		"escape characters are allowed: "+ |  | ||||||
| 		`\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r) | 		`\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -711,8 +826,9 @@ func lexShortUnicodeEscape(lx *lexer) stateFn { | ||||||
| 	for i := 0; i < 4; i++ { | 	for i := 0; i < 4; i++ { | ||||||
| 		r = lx.next() | 		r = lx.next() | ||||||
| 		if !isHexadecimal(r) { | 		if !isHexadecimal(r) { | ||||||
| 			return lx.errorf(`expected four hexadecimal digits after '\u', `+ | 			return lx.errorf( | ||||||
| 				"but got %q instead", lx.current()) | 				`expected four hexadecimal digits after '\u', but got %q instead`, | ||||||
|  | 				lx.current()) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return lx.pop() | 	return lx.pop() | ||||||
|  | @ -723,28 +839,33 @@ func lexLongUnicodeEscape(lx *lexer) stateFn { | ||||||
| 	for i := 0; i < 8; i++ { | 	for i := 0; i < 8; i++ { | ||||||
| 		r = lx.next() | 		r = lx.next() | ||||||
| 		if !isHexadecimal(r) { | 		if !isHexadecimal(r) { | ||||||
| 			return lx.errorf(`expected eight hexadecimal digits after '\U', `+ | 			return lx.errorf( | ||||||
| 				"but got %q instead", lx.current()) | 				`expected eight hexadecimal digits after '\U', but got %q instead`, | ||||||
|  | 				lx.current()) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return lx.pop() | 	return lx.pop() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // lexNumberOrDateStart consumes either an integer, a float, or datetime.
 | // lexNumberOrDateStart processes the first character of a value which begins
 | ||||||
|  | // with a digit. It exists to catch values starting with '0', so that
 | ||||||
|  | // lexBaseNumberOrDate can differentiate base prefixed integers from other
 | ||||||
|  | // types.
 | ||||||
| func lexNumberOrDateStart(lx *lexer) stateFn { | func lexNumberOrDateStart(lx *lexer) stateFn { | ||||||
| 	r := lx.next() | 	r := lx.next() | ||||||
| 	if isDigit(r) { |  | ||||||
| 		return lexNumberOrDate |  | ||||||
| 	} |  | ||||||
| 	switch r { | 	switch r { | ||||||
| 	case '_': | 	case '0': | ||||||
| 		return lexNumber | 		return lexBaseNumberOrDate | ||||||
| 	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) | 
 | ||||||
|  | 	if !isDigit(r) { | ||||||
|  | 		// The only way to reach this state is if the value starts
 | ||||||
|  | 		// with a digit, so specifically treat anything else as an
 | ||||||
|  | 		// error.
 | ||||||
|  | 		return lx.errorf("expected a digit but got %q", r) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return lexNumberOrDate | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // lexNumberOrDate consumes either an integer, float or datetime.
 | // lexNumberOrDate consumes either an integer, float or datetime.
 | ||||||
|  | @ -754,10 +875,10 @@ func lexNumberOrDate(lx *lexer) stateFn { | ||||||
| 		return lexNumberOrDate | 		return lexNumberOrDate | ||||||
| 	} | 	} | ||||||
| 	switch r { | 	switch r { | ||||||
| 	case '-': | 	case '-', ':': | ||||||
| 		return lexDatetime | 		return lexDatetime | ||||||
| 	case '_': | 	case '_': | ||||||
| 		return lexNumber | 		return lexDecimalNumber | ||||||
| 	case '.', 'e', 'E': | 	case '.', 'e', 'E': | ||||||
| 		return lexFloat | 		return lexFloat | ||||||
| 	} | 	} | ||||||
|  | @ -775,41 +896,156 @@ func lexDatetime(lx *lexer) stateFn { | ||||||
| 		return lexDatetime | 		return lexDatetime | ||||||
| 	} | 	} | ||||||
| 	switch r { | 	switch r { | ||||||
| 	case '-', 'T', ':', '.', 'Z', '+': | 	case '-', ':', 'T', 't', ' ', '.', 'Z', 'z', '+': | ||||||
| 		return lexDatetime | 		return lexDatetime | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	lx.backup() | 	lx.backup() | ||||||
| 	lx.emit(itemDatetime) | 	lx.emitTrim(itemDatetime) | ||||||
| 	return lx.pop() | 	return lx.pop() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // lexNumberStart consumes either an integer or a float. It assumes that a sign
 | // lexHexInteger consumes a hexadecimal integer after seeing the '0x' prefix.
 | ||||||
| // has already been read, but that *no* digits have been consumed.
 | func lexHexInteger(lx *lexer) stateFn { | ||||||
| // 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() | 	r := lx.next() | ||||||
| 	if !isDigit(r) { | 	if isHexadecimal(r) { | ||||||
| 		if r == '.' { | 		return lexHexInteger | ||||||
| 			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 { | 	switch r { | ||||||
| 	case '_': | 	case '_': | ||||||
| 		return lexNumber | 		return lexHexInteger | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	lx.backup() | ||||||
|  | 	lx.emit(itemInteger) | ||||||
|  | 	return lx.pop() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // lexOctalInteger consumes an octal integer after seeing the '0o' prefix.
 | ||||||
|  | func lexOctalInteger(lx *lexer) stateFn { | ||||||
|  | 	r := lx.next() | ||||||
|  | 	if isOctal(r) { | ||||||
|  | 		return lexOctalInteger | ||||||
|  | 	} | ||||||
|  | 	switch r { | ||||||
|  | 	case '_': | ||||||
|  | 		return lexOctalInteger | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	lx.backup() | ||||||
|  | 	lx.emit(itemInteger) | ||||||
|  | 	return lx.pop() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // lexBinaryInteger consumes a binary integer after seeing the '0b' prefix.
 | ||||||
|  | func lexBinaryInteger(lx *lexer) stateFn { | ||||||
|  | 	r := lx.next() | ||||||
|  | 	if isBinary(r) { | ||||||
|  | 		return lexBinaryInteger | ||||||
|  | 	} | ||||||
|  | 	switch r { | ||||||
|  | 	case '_': | ||||||
|  | 		return lexBinaryInteger | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	lx.backup() | ||||||
|  | 	lx.emit(itemInteger) | ||||||
|  | 	return lx.pop() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // lexDecimalNumber consumes a decimal float or integer.
 | ||||||
|  | func lexDecimalNumber(lx *lexer) stateFn { | ||||||
|  | 	r := lx.next() | ||||||
|  | 	if isDigit(r) { | ||||||
|  | 		return lexDecimalNumber | ||||||
|  | 	} | ||||||
|  | 	switch r { | ||||||
| 	case '.', 'e', 'E': | 	case '.', 'e', 'E': | ||||||
| 		return lexFloat | 		return lexFloat | ||||||
|  | 	case '_': | ||||||
|  | 		return lexDecimalNumber | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	lx.backup() | ||||||
|  | 	lx.emit(itemInteger) | ||||||
|  | 	return lx.pop() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // lexDecimalNumber consumes the first digit of a number beginning with a sign.
 | ||||||
|  | // It assumes the sign has already been consumed. Values which start with a sign
 | ||||||
|  | // are only allowed to be decimal integers or floats.
 | ||||||
|  | //
 | ||||||
|  | // The special "nan" and "inf" values are also recognized.
 | ||||||
|  | func lexDecimalNumberStart(lx *lexer) stateFn { | ||||||
|  | 	r := lx.next() | ||||||
|  | 
 | ||||||
|  | 	// Special error cases to give users better error messages
 | ||||||
|  | 	switch r { | ||||||
|  | 	case 'i': | ||||||
|  | 		if !lx.accept('n') || !lx.accept('f') { | ||||||
|  | 			return lx.errorf("invalid float: '%s'", lx.current()) | ||||||
|  | 		} | ||||||
|  | 		lx.emit(itemFloat) | ||||||
|  | 		return lx.pop() | ||||||
|  | 	case 'n': | ||||||
|  | 		if !lx.accept('a') || !lx.accept('n') { | ||||||
|  | 			return lx.errorf("invalid float: '%s'", lx.current()) | ||||||
|  | 		} | ||||||
|  | 		lx.emit(itemFloat) | ||||||
|  | 		return lx.pop() | ||||||
|  | 	case '0': | ||||||
|  | 		p := lx.peek() | ||||||
|  | 		switch p { | ||||||
|  | 		case 'b', 'o', 'x': | ||||||
|  | 			return lx.errorf("cannot use sign with non-decimal numbers: '%s%c'", lx.current(), p) | ||||||
|  | 		} | ||||||
|  | 	case '.': | ||||||
|  | 		return lx.errorf("floats must start with a digit, not '.'") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if isDigit(r) { | ||||||
|  | 		return lexDecimalNumber | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return lx.errorf("expected a digit but got %q", r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // lexBaseNumberOrDate differentiates between the possible values which
 | ||||||
|  | // start with '0'. It assumes that before reaching this state, the initial '0'
 | ||||||
|  | // has been consumed.
 | ||||||
|  | func lexBaseNumberOrDate(lx *lexer) stateFn { | ||||||
|  | 	r := lx.next() | ||||||
|  | 	// Note: All datetimes start with at least two digits, so we don't
 | ||||||
|  | 	// handle date characters (':', '-', etc.) here.
 | ||||||
|  | 	if isDigit(r) { | ||||||
|  | 		return lexNumberOrDate | ||||||
|  | 	} | ||||||
|  | 	switch r { | ||||||
|  | 	case '_': | ||||||
|  | 		// Can only be decimal, because there can't be an underscore
 | ||||||
|  | 		// between the '0' and the base designator, and dates can't
 | ||||||
|  | 		// contain underscores.
 | ||||||
|  | 		return lexDecimalNumber | ||||||
|  | 	case '.', 'e', 'E': | ||||||
|  | 		return lexFloat | ||||||
|  | 	case 'b': | ||||||
|  | 		r = lx.peek() | ||||||
|  | 		if !isBinary(r) { | ||||||
|  | 			lx.errorf("not a binary number: '%s%c'", lx.current(), r) | ||||||
|  | 		} | ||||||
|  | 		return lexBinaryInteger | ||||||
|  | 	case 'o': | ||||||
|  | 		r = lx.peek() | ||||||
|  | 		if !isOctal(r) { | ||||||
|  | 			lx.errorf("not an octal number: '%s%c'", lx.current(), r) | ||||||
|  | 		} | ||||||
|  | 		return lexOctalInteger | ||||||
|  | 	case 'x': | ||||||
|  | 		r = lx.peek() | ||||||
|  | 		if !isHexadecimal(r) { | ||||||
|  | 			lx.errorf("not a hexidecimal number: '%s%c'", lx.current(), r) | ||||||
|  | 		} | ||||||
|  | 		return lexHexInteger | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	lx.backup() | 	lx.backup() | ||||||
|  | @ -867,21 +1103,22 @@ func lexCommentStart(lx *lexer) stateFn { | ||||||
| // It will consume *up to* the first newline character, and pass control
 | // It will consume *up to* the first newline character, and pass control
 | ||||||
| // back to the last state on the stack.
 | // back to the last state on the stack.
 | ||||||
| func lexComment(lx *lexer) stateFn { | func lexComment(lx *lexer) stateFn { | ||||||
| 	r := lx.peek() | 	switch r := lx.next(); { | ||||||
| 	if isNL(r) || r == eof { | 	case isNL(r) || r == eof: | ||||||
|  | 		lx.backup() | ||||||
| 		lx.emit(itemText) | 		lx.emit(itemText) | ||||||
| 		return lx.pop() | 		return lx.pop() | ||||||
|  | 	case isControl(r): | ||||||
|  | 		return lx.errorf("control characters are not allowed inside comments: '0x%02x'", r) | ||||||
|  | 	default: | ||||||
|  | 		return lexComment | ||||||
| 	} | 	} | ||||||
| 	lx.next() |  | ||||||
| 	return lexComment |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // lexSkip ignores all slurped input and moves on to the next state.
 | // lexSkip ignores all slurped input and moves on to the next state.
 | ||||||
| func lexSkip(lx *lexer, nextState stateFn) stateFn { | func lexSkip(lx *lexer, nextState stateFn) stateFn { | ||||||
| 	return func(lx *lexer) stateFn { | 	lx.ignore() | ||||||
| 		lx.ignore() | 	return nextState | ||||||
| 		return nextState |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // isWhitespace returns true if `r` is a whitespace character according
 | // isWhitespace returns true if `r` is a whitespace character according
 | ||||||
|  | @ -894,6 +1131,16 @@ func isNL(r rune) bool { | ||||||
| 	return r == '\n' || r == '\r' | 	return r == '\n' || r == '\r' | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Control characters except \n, \t
 | ||||||
|  | func isControl(r rune) bool { | ||||||
|  | 	switch r { | ||||||
|  | 	case '\t', '\r', '\n': | ||||||
|  | 		return false | ||||||
|  | 	default: | ||||||
|  | 		return (r >= 0x00 && r <= 0x1f) || r == 0x7f | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func isDigit(r rune) bool { | func isDigit(r rune) bool { | ||||||
| 	return r >= '0' && r <= '9' | 	return r >= '0' && r <= '9' | ||||||
| } | } | ||||||
|  | @ -904,6 +1151,14 @@ func isHexadecimal(r rune) bool { | ||||||
| 		(r >= 'A' && r <= 'F') | 		(r >= 'A' && r <= 'F') | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func isOctal(r rune) bool { | ||||||
|  | 	return r >= '0' && r <= '7' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isBinary(r rune) bool { | ||||||
|  | 	return r == '0' || r == '1' | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func isBareKeyChar(r rune) bool { | func isBareKeyChar(r rune) bool { | ||||||
| 	return (r >= 'A' && r <= 'Z') || | 	return (r >= 'A' && r <= 'Z') || | ||||||
| 		(r >= 'a' && r <= 'z') || | 		(r >= 'a' && r <= 'z') || | ||||||
|  | @ -912,6 +1167,17 @@ func isBareKeyChar(r rune) bool { | ||||||
| 		r == '-' | 		r == '-' | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (s stateFn) String() string { | ||||||
|  | 	name := runtime.FuncForPC(reflect.ValueOf(s).Pointer()).Name() | ||||||
|  | 	if i := strings.LastIndexByte(name, '.'); i > -1 { | ||||||
|  | 		name = name[i+1:] | ||||||
|  | 	} | ||||||
|  | 	if s == nil { | ||||||
|  | 		name = "<nil>" | ||||||
|  | 	} | ||||||
|  | 	return name + "()" | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (itype itemType) String() string { | func (itype itemType) String() string { | ||||||
| 	switch itype { | 	switch itype { | ||||||
| 	case itemError: | 	case itemError: | ||||||
|  | @ -938,12 +1204,18 @@ func (itype itemType) String() string { | ||||||
| 		return "TableEnd" | 		return "TableEnd" | ||||||
| 	case itemKeyStart: | 	case itemKeyStart: | ||||||
| 		return "KeyStart" | 		return "KeyStart" | ||||||
|  | 	case itemKeyEnd: | ||||||
|  | 		return "KeyEnd" | ||||||
| 	case itemArray: | 	case itemArray: | ||||||
| 		return "Array" | 		return "Array" | ||||||
| 	case itemArrayEnd: | 	case itemArrayEnd: | ||||||
| 		return "ArrayEnd" | 		return "ArrayEnd" | ||||||
| 	case itemCommentStart: | 	case itemCommentStart: | ||||||
| 		return "CommentStart" | 		return "CommentStart" | ||||||
|  | 	case itemInlineTableStart: | ||||||
|  | 		return "InlineTableStart" | ||||||
|  | 	case itemInlineTableEnd: | ||||||
|  | 		return "InlineTableEnd" | ||||||
| 	} | 	} | ||||||
| 	panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype))) | 	panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype))) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,12 +1,14 @@ | ||||||
| package toml | package toml | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 	"unicode" |  | ||||||
| 	"unicode/utf8" | 	"unicode/utf8" | ||||||
|  | 
 | ||||||
|  | 	"github.com/BurntSushi/toml/internal" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type parser struct { | type parser struct { | ||||||
|  | @ -14,39 +16,54 @@ type parser struct { | ||||||
| 	types   map[string]tomlType | 	types   map[string]tomlType | ||||||
| 	lx      *lexer | 	lx      *lexer | ||||||
| 
 | 
 | ||||||
| 	// A list of keys in the order that they appear in the TOML data.
 | 	ordered    []Key           // List of keys in the order that they appear in the TOML data.
 | ||||||
| 	ordered []Key | 	context    Key             // Full key for the current hash in scope.
 | ||||||
| 
 | 	currentKey string          // Base key name for everything except hashes.
 | ||||||
| 	// the full key for the current hash in scope
 | 	approxLine int             // Rough approximation of line number
 | ||||||
| 	context Key | 	implicits  map[string]bool // Record implied keys (e.g. 'key.group.names').
 | ||||||
| 
 |  | ||||||
| 	// 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 | // ParseError is used when a file can't be parsed: for example invalid integer
 | ||||||
|  | // literals, duplicate keys, etc.
 | ||||||
|  | type ParseError struct { | ||||||
|  | 	Message string | ||||||
|  | 	Line    int | ||||||
|  | 	LastKey string | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| func (pe parseError) Error() string { | func (pe ParseError) Error() string { | ||||||
| 	return string(pe) | 	return fmt.Sprintf("Near line %d (last key parsed '%s'): %s", | ||||||
|  | 		pe.Line, pe.LastKey, pe.Message) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func parse(data string) (p *parser, err error) { | func parse(data string) (p *parser, err error) { | ||||||
| 	defer func() { | 	defer func() { | ||||||
| 		if r := recover(); r != nil { | 		if r := recover(); r != nil { | ||||||
| 			var ok bool | 			var ok bool | ||||||
| 			if err, ok = r.(parseError); ok { | 			if err, ok = r.(ParseError); ok { | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 			panic(r) | 			panic(r) | ||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
|  | 	// Read over BOM; do this here as the lexer calls utf8.DecodeRuneInString()
 | ||||||
|  | 	// which mangles stuff.
 | ||||||
|  | 	if strings.HasPrefix(data, "\xff\xfe") || strings.HasPrefix(data, "\xfe\xff") { | ||||||
|  | 		data = data[2:] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Examine first few bytes for NULL bytes; this probably means it's a UTF-16
 | ||||||
|  | 	// file (second byte in surrogate pair being NULL). Again, do this here to
 | ||||||
|  | 	// avoid having to deal with UTF-8/16 stuff in the lexer.
 | ||||||
|  | 	ex := 6 | ||||||
|  | 	if len(data) < 6 { | ||||||
|  | 		ex = len(data) | ||||||
|  | 	} | ||||||
|  | 	if strings.ContainsRune(data[:ex], 0) { | ||||||
|  | 		return nil, errors.New("files cannot contain NULL bytes; probably using UTF-16; TOML files must be UTF-8") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	p = &parser{ | 	p = &parser{ | ||||||
| 		mapping:   make(map[string]interface{}), | 		mapping:   make(map[string]interface{}), | ||||||
| 		types:     make(map[string]tomlType), | 		types:     make(map[string]tomlType), | ||||||
|  | @ -66,13 +83,17 @@ func parse(data string) (p *parser, err error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *parser) panicf(format string, v ...interface{}) { | func (p *parser) panicf(format string, v ...interface{}) { | ||||||
| 	msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s", | 	msg := fmt.Sprintf(format, v...) | ||||||
| 		p.approxLine, p.current(), fmt.Sprintf(format, v...)) | 	panic(ParseError{ | ||||||
| 	panic(parseError(msg)) | 		Message: msg, | ||||||
|  | 		Line:    p.approxLine, | ||||||
|  | 		LastKey: p.current(), | ||||||
|  | 	}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *parser) next() item { | func (p *parser) next() item { | ||||||
| 	it := p.lx.nextItem() | 	it := p.lx.nextItem() | ||||||
|  | 	//fmt.Printf("ITEM %-18s line %-3d │ %q\n", it.typ, it.line, it.val)
 | ||||||
| 	if it.typ == itemError { | 	if it.typ == itemError { | ||||||
| 		p.panicf("%s", it.val) | 		p.panicf("%s", it.val) | ||||||
| 	} | 	} | ||||||
|  | @ -97,44 +118,63 @@ func (p *parser) assertEqual(expected, got itemType) { | ||||||
| 
 | 
 | ||||||
| func (p *parser) topLevel(item item) { | func (p *parser) topLevel(item item) { | ||||||
| 	switch item.typ { | 	switch item.typ { | ||||||
| 	case itemCommentStart: | 	case itemCommentStart: // # ..
 | ||||||
| 		p.approxLine = item.line | 		p.approxLine = item.line | ||||||
| 		p.expect(itemText) | 		p.expect(itemText) | ||||||
| 	case itemTableStart: | 	case itemTableStart: // [ .. ]
 | ||||||
| 		kg := p.next() | 		name := p.next() | ||||||
| 		p.approxLine = kg.line | 		p.approxLine = name.line | ||||||
| 
 | 
 | ||||||
| 		var key Key | 		var key Key | ||||||
| 		for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() { | 		for ; name.typ != itemTableEnd && name.typ != itemEOF; name = p.next() { | ||||||
| 			key = append(key, p.keyString(kg)) | 			key = append(key, p.keyString(name)) | ||||||
| 		} | 		} | ||||||
| 		p.assertEqual(itemTableEnd, kg.typ) | 		p.assertEqual(itemTableEnd, name.typ) | ||||||
| 
 | 
 | ||||||
| 		p.establishContext(key, false) | 		p.addContext(key, false) | ||||||
| 		p.setType("", tomlHash) | 		p.setType("", tomlHash) | ||||||
| 		p.ordered = append(p.ordered, key) | 		p.ordered = append(p.ordered, key) | ||||||
| 	case itemArrayTableStart: | 	case itemArrayTableStart: // [[ .. ]]
 | ||||||
| 		kg := p.next() | 		name := p.next() | ||||||
| 		p.approxLine = kg.line | 		p.approxLine = name.line | ||||||
| 
 | 
 | ||||||
| 		var key Key | 		var key Key | ||||||
| 		for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() { | 		for ; name.typ != itemArrayTableEnd && name.typ != itemEOF; name = p.next() { | ||||||
| 			key = append(key, p.keyString(kg)) | 			key = append(key, p.keyString(name)) | ||||||
| 		} | 		} | ||||||
| 		p.assertEqual(itemArrayTableEnd, kg.typ) | 		p.assertEqual(itemArrayTableEnd, name.typ) | ||||||
| 
 | 
 | ||||||
| 		p.establishContext(key, true) | 		p.addContext(key, true) | ||||||
| 		p.setType("", tomlArrayHash) | 		p.setType("", tomlArrayHash) | ||||||
| 		p.ordered = append(p.ordered, key) | 		p.ordered = append(p.ordered, key) | ||||||
| 	case itemKeyStart: | 	case itemKeyStart: // key = ..
 | ||||||
| 		kname := p.next() | 		outerContext := p.context | ||||||
| 		p.approxLine = kname.line | 		/// Read all the key parts (e.g. 'a' and 'b' in 'a.b')
 | ||||||
| 		p.currentKey = p.keyString(kname) | 		k := p.next() | ||||||
|  | 		p.approxLine = k.line | ||||||
|  | 		var key Key | ||||||
|  | 		for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() { | ||||||
|  | 			key = append(key, p.keyString(k)) | ||||||
|  | 		} | ||||||
|  | 		p.assertEqual(itemKeyEnd, k.typ) | ||||||
| 
 | 
 | ||||||
| 		val, typ := p.value(p.next()) | 		/// The current key is the last part.
 | ||||||
| 		p.setValue(p.currentKey, val) | 		p.currentKey = key[len(key)-1] | ||||||
| 		p.setType(p.currentKey, typ) | 
 | ||||||
|  | 		/// All the other parts (if any) are the context; need to set each part
 | ||||||
|  | 		/// as implicit.
 | ||||||
|  | 		context := key[:len(key)-1] | ||||||
|  | 		for i := range context { | ||||||
|  | 			p.addImplicitContext(append(p.context, context[i:i+1]...)) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		/// Set value.
 | ||||||
|  | 		val, typ := p.value(p.next(), false) | ||||||
|  | 		p.set(p.currentKey, val, typ) | ||||||
| 		p.ordered = append(p.ordered, p.context.add(p.currentKey)) | 		p.ordered = append(p.ordered, p.context.add(p.currentKey)) | ||||||
|  | 
 | ||||||
|  | 		/// Remove the context we added (preserving any context from [tbl] lines).
 | ||||||
|  | 		p.context = outerContext | ||||||
| 		p.currentKey = "" | 		p.currentKey = "" | ||||||
| 	default: | 	default: | ||||||
| 		p.bug("Unexpected type at top level: %s", item.typ) | 		p.bug("Unexpected type at top level: %s", item.typ) | ||||||
|  | @ -148,180 +188,253 @@ func (p *parser) keyString(it item) string { | ||||||
| 		return it.val | 		return it.val | ||||||
| 	case itemString, itemMultilineString, | 	case itemString, itemMultilineString, | ||||||
| 		itemRawString, itemRawMultilineString: | 		itemRawString, itemRawMultilineString: | ||||||
| 		s, _ := p.value(it) | 		s, _ := p.value(it, false) | ||||||
| 		return s.(string) | 		return s.(string) | ||||||
| 	default: | 	default: | ||||||
| 		p.bug("Unexpected key type: %s", it.typ) | 		p.bug("Unexpected key type: %s", it.typ) | ||||||
| 		panic("unreachable") |  | ||||||
| 	} | 	} | ||||||
|  | 	panic("unreachable") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var datetimeRepl = strings.NewReplacer( | ||||||
|  | 	"z", "Z", | ||||||
|  | 	"t", "T", | ||||||
|  | 	" ", "T") | ||||||
|  | 
 | ||||||
| // value translates an expected value from the lexer into a Go value wrapped
 | // value translates an expected value from the lexer into a Go value wrapped
 | ||||||
| // as an empty interface.
 | // as an empty interface.
 | ||||||
| func (p *parser) value(it item) (interface{}, tomlType) { | func (p *parser) value(it item, parentIsArray bool) (interface{}, tomlType) { | ||||||
| 	switch it.typ { | 	switch it.typ { | ||||||
| 	case itemString: | 	case itemString: | ||||||
| 		return p.replaceEscapes(it.val), p.typeOfPrimitive(it) | 		return p.replaceEscapes(it.val), p.typeOfPrimitive(it) | ||||||
| 	case itemMultilineString: | 	case itemMultilineString: | ||||||
| 		trimmed := stripFirstNewline(stripEscapedWhitespace(it.val)) | 		return p.replaceEscapes(stripFirstNewline(stripEscapedNewlines(it.val))), p.typeOfPrimitive(it) | ||||||
| 		return p.replaceEscapes(trimmed), p.typeOfPrimitive(it) |  | ||||||
| 	case itemRawString: | 	case itemRawString: | ||||||
| 		return it.val, p.typeOfPrimitive(it) | 		return it.val, p.typeOfPrimitive(it) | ||||||
| 	case itemRawMultilineString: | 	case itemRawMultilineString: | ||||||
| 		return stripFirstNewline(it.val), p.typeOfPrimitive(it) | 		return stripFirstNewline(it.val), p.typeOfPrimitive(it) | ||||||
|  | 	case itemInteger: | ||||||
|  | 		return p.valueInteger(it) | ||||||
|  | 	case itemFloat: | ||||||
|  | 		return p.valueFloat(it) | ||||||
| 	case itemBool: | 	case itemBool: | ||||||
| 		switch it.val { | 		switch it.val { | ||||||
| 		case "true": | 		case "true": | ||||||
| 			return true, p.typeOfPrimitive(it) | 			return true, p.typeOfPrimitive(it) | ||||||
| 		case "false": | 		case "false": | ||||||
| 			return false, p.typeOfPrimitive(it) | 			return false, p.typeOfPrimitive(it) | ||||||
|  | 		default: | ||||||
|  | 			p.bug("Expected boolean value, but got '%s'.", it.val) | ||||||
| 		} | 		} | ||||||
| 		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: | 	case itemDatetime: | ||||||
| 		var t time.Time | 		return p.valueDatetime(it) | ||||||
| 		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: | 	case itemArray: | ||||||
| 		array := make([]interface{}, 0) | 		return p.valueArray(it) | ||||||
| 		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: | 	case itemInlineTableStart: | ||||||
| 		var ( | 		return p.valueInlineTable(it, parentIsArray) | ||||||
| 			hash         = make(map[string]interface{}) | 	default: | ||||||
| 			outerContext = p.context | 		p.bug("Unexpected value type: %s", it.typ) | ||||||
| 			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") | 	panic("unreachable") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (p *parser) valueInteger(it item) (interface{}, tomlType) { | ||||||
|  | 	if !numUnderscoresOK(it.val) { | ||||||
|  | 		p.panicf("Invalid integer %q: underscores must be surrounded by digits", it.val) | ||||||
|  | 	} | ||||||
|  | 	if numHasLeadingZero(it.val) { | ||||||
|  | 		p.panicf("Invalid integer %q: cannot have leading zeroes", it.val) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	num, err := strconv.ParseInt(it.val, 0, 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) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *parser) valueFloat(it item) (interface{}, tomlType) { | ||||||
|  | 	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 len(parts) > 0 && numHasLeadingZero(parts[0]) { | ||||||
|  | 		p.panicf("Invalid float %q: cannot have leading zeroes", 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) | ||||||
|  | 	if val == "+nan" || val == "-nan" { // Go doesn't support this, but TOML spec does.
 | ||||||
|  | 		val = "nan" | ||||||
|  | 	} | ||||||
|  | 	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) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var dtTypes = []struct { | ||||||
|  | 	fmt  string | ||||||
|  | 	zone *time.Location | ||||||
|  | }{ | ||||||
|  | 	{time.RFC3339Nano, time.Local}, | ||||||
|  | 	{"2006-01-02T15:04:05.999999999", internal.LocalDatetime}, | ||||||
|  | 	{"2006-01-02", internal.LocalDate}, | ||||||
|  | 	{"15:04:05.999999999", internal.LocalTime}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *parser) valueDatetime(it item) (interface{}, tomlType) { | ||||||
|  | 	it.val = datetimeRepl.Replace(it.val) | ||||||
|  | 	var ( | ||||||
|  | 		t   time.Time | ||||||
|  | 		ok  bool | ||||||
|  | 		err error | ||||||
|  | 	) | ||||||
|  | 	for _, dt := range dtTypes { | ||||||
|  | 		t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone) | ||||||
|  | 		if err == nil { | ||||||
|  | 			ok = true | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if !ok { | ||||||
|  | 		p.panicf("Invalid TOML Datetime: %q.", it.val) | ||||||
|  | 	} | ||||||
|  | 	return t, p.typeOfPrimitive(it) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *parser) valueArray(it item) (interface{}, tomlType) { | ||||||
|  | 	p.setType(p.currentKey, tomlArray) | ||||||
|  | 
 | ||||||
|  | 	// p.setType(p.currentKey, typ)
 | ||||||
|  | 	var ( | ||||||
|  | 		array []interface{} | ||||||
|  | 		types []tomlType | ||||||
|  | 	) | ||||||
|  | 	for it = p.next(); it.typ != itemArrayEnd; it = p.next() { | ||||||
|  | 		if it.typ == itemCommentStart { | ||||||
|  | 			p.expect(itemText) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		val, typ := p.value(it, true) | ||||||
|  | 		array = append(array, val) | ||||||
|  | 		types = append(types, typ) | ||||||
|  | 	} | ||||||
|  | 	return array, tomlArray | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *parser) valueInlineTable(it item, parentIsArray bool) (interface{}, tomlType) { | ||||||
|  | 	var ( | ||||||
|  | 		hash         = make(map[string]interface{}) | ||||||
|  | 		outerContext = p.context | ||||||
|  | 		outerKey     = p.currentKey | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	p.context = append(p.context, p.currentKey) | ||||||
|  | 	prevContext := p.context | ||||||
|  | 	p.currentKey = "" | ||||||
|  | 
 | ||||||
|  | 	p.addImplicit(p.context) | ||||||
|  | 	p.addContext(p.context, parentIsArray) | ||||||
|  | 
 | ||||||
|  | 	/// Loop over all table key/value pairs.
 | ||||||
|  | 	for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() { | ||||||
|  | 		if it.typ == itemCommentStart { | ||||||
|  | 			p.expect(itemText) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		/// Read all key parts.
 | ||||||
|  | 		k := p.next() | ||||||
|  | 		p.approxLine = k.line | ||||||
|  | 		var key Key | ||||||
|  | 		for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() { | ||||||
|  | 			key = append(key, p.keyString(k)) | ||||||
|  | 		} | ||||||
|  | 		p.assertEqual(itemKeyEnd, k.typ) | ||||||
|  | 
 | ||||||
|  | 		/// The current key is the last part.
 | ||||||
|  | 		p.currentKey = key[len(key)-1] | ||||||
|  | 
 | ||||||
|  | 		/// All the other parts (if any) are the context; need to set each part
 | ||||||
|  | 		/// as implicit.
 | ||||||
|  | 		context := key[:len(key)-1] | ||||||
|  | 		for i := range context { | ||||||
|  | 			p.addImplicitContext(append(p.context, context[i:i+1]...)) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		/// Set the value.
 | ||||||
|  | 		val, typ := p.value(p.next(), false) | ||||||
|  | 		p.set(p.currentKey, val, typ) | ||||||
|  | 		p.ordered = append(p.ordered, p.context.add(p.currentKey)) | ||||||
|  | 		hash[p.currentKey] = val | ||||||
|  | 
 | ||||||
|  | 		/// Restore context.
 | ||||||
|  | 		p.context = prevContext | ||||||
|  | 	} | ||||||
|  | 	p.context = outerContext | ||||||
|  | 	p.currentKey = outerKey | ||||||
|  | 	return hash, tomlHash | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // numHasLeadingZero checks if this number has leading zeroes, allowing for '0',
 | ||||||
|  | // +/- signs, and base prefixes.
 | ||||||
|  | func numHasLeadingZero(s string) bool { | ||||||
|  | 	if len(s) > 1 && s[0] == '0' && isDigit(rune(s[1])) { // >1 to allow "0" and isDigit to allow 0x
 | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if len(s) > 2 && (s[0] == '-' || s[0] == '+') && s[1] == '0' { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // numUnderscoresOK checks whether each underscore in s is surrounded by
 | // numUnderscoresOK checks whether each underscore in s is surrounded by
 | ||||||
| // characters that are not underscores.
 | // characters that are not underscores.
 | ||||||
| func numUnderscoresOK(s string) bool { | func numUnderscoresOK(s string) bool { | ||||||
|  | 	switch s { | ||||||
|  | 	case "nan", "+nan", "-nan", "inf", "-inf", "+inf": | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
| 	accept := false | 	accept := false | ||||||
| 	for _, r := range s { | 	for _, r := range s { | ||||||
| 		if r == '_' { | 		if r == '_' { | ||||||
| 			if !accept { | 			if !accept { | ||||||
| 				return false | 				return false | ||||||
| 			} | 			} | ||||||
| 			accept = false |  | ||||||
| 			continue |  | ||||||
| 		} | 		} | ||||||
| 		accept = true | 
 | ||||||
|  | 		// isHexadecimal is a superset of all the permissable characters
 | ||||||
|  | 		// surrounding an underscore.
 | ||||||
|  | 		accept = isHexadecimal(r) | ||||||
| 	} | 	} | ||||||
| 	return accept | 	return accept | ||||||
| } | } | ||||||
|  | @ -338,13 +451,12 @@ func numPeriodsOK(s string) bool { | ||||||
| 	return !period | 	return !period | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // establishContext sets the current context of the parser,
 | // Set the current context of the parser, where the context is either a hash or
 | ||||||
| // where the context is either a hash or an array of hashes. Which one is
 | // an array of hashes, depending on the value of the `array` parameter.
 | ||||||
| // set depends on the value of the `array` parameter.
 |  | ||||||
| //
 | //
 | ||||||
| // Establishing the context also makes sure that the key isn't a duplicate, and
 | // Establishing the context also makes sure that the key isn't a duplicate, and
 | ||||||
| // will create implicit hashes automatically.
 | // will create implicit hashes automatically.
 | ||||||
| func (p *parser) establishContext(key Key, array bool) { | func (p *parser) addContext(key Key, array bool) { | ||||||
| 	var ok bool | 	var ok bool | ||||||
| 
 | 
 | ||||||
| 	// Always start at the top level and drill down for our context.
 | 	// Always start at the top level and drill down for our context.
 | ||||||
|  | @ -383,7 +495,7 @@ func (p *parser) establishContext(key Key, array bool) { | ||||||
| 		// list of tables for it.
 | 		// list of tables for it.
 | ||||||
| 		k := key[len(key)-1] | 		k := key[len(key)-1] | ||||||
| 		if _, ok := hashContext[k]; !ok { | 		if _, ok := hashContext[k]; !ok { | ||||||
| 			hashContext[k] = make([]map[string]interface{}, 0, 5) | 			hashContext[k] = make([]map[string]interface{}, 0, 4) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Add a new table. But make sure the key hasn't already been used
 | 		// Add a new table. But make sure the key hasn't already been used
 | ||||||
|  | @ -391,8 +503,7 @@ func (p *parser) establishContext(key Key, array bool) { | ||||||
| 		if hash, ok := hashContext[k].([]map[string]interface{}); ok { | 		if hash, ok := hashContext[k].([]map[string]interface{}); ok { | ||||||
| 			hashContext[k] = append(hash, make(map[string]interface{})) | 			hashContext[k] = append(hash, make(map[string]interface{})) | ||||||
| 		} else { | 		} else { | ||||||
| 			p.panicf("Key '%s' was already created and cannot be used as "+ | 			p.panicf("Key '%s' was already created and cannot be used as an array.", keyContext) | ||||||
| 				"an array.", keyContext) |  | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		p.setValue(key[len(key)-1], make(map[string]interface{})) | 		p.setValue(key[len(key)-1], make(map[string]interface{})) | ||||||
|  | @ -400,15 +511,22 @@ func (p *parser) establishContext(key Key, array bool) { | ||||||
| 	p.context = append(p.context, key[len(key)-1]) | 	p.context = append(p.context, key[len(key)-1]) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // set calls setValue and setType.
 | ||||||
|  | func (p *parser) set(key string, val interface{}, typ tomlType) { | ||||||
|  | 	p.setValue(p.currentKey, val) | ||||||
|  | 	p.setType(p.currentKey, typ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // setValue sets the given key to the given value in the current context.
 | // 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
 | // It will make sure that the key hasn't already been defined, account for
 | ||||||
| // implicit key groups.
 | // implicit key groups.
 | ||||||
| func (p *parser) setValue(key string, value interface{}) { | func (p *parser) setValue(key string, value interface{}) { | ||||||
| 	var tmpHash interface{} | 	var ( | ||||||
| 	var ok bool | 		tmpHash    interface{} | ||||||
| 
 | 		ok         bool | ||||||
| 	hash := p.mapping | 		hash       = p.mapping | ||||||
| 	keyContext := make(Key, 0) | 		keyContext Key | ||||||
|  | 	) | ||||||
| 	for _, k := range p.context { | 	for _, k := range p.context { | ||||||
| 		keyContext = append(keyContext, k) | 		keyContext = append(keyContext, k) | ||||||
| 		if tmpHash, ok = hash[k]; !ok { | 		if tmpHash, ok = hash[k]; !ok { | ||||||
|  | @ -422,24 +540,26 @@ func (p *parser) setValue(key string, value interface{}) { | ||||||
| 		case map[string]interface{}: | 		case map[string]interface{}: | ||||||
| 			hash = t | 			hash = t | ||||||
| 		default: | 		default: | ||||||
| 			p.bug("Expected hash to have type 'map[string]interface{}', but "+ | 			p.panicf("Key '%s' has already been defined.", keyContext) | ||||||
| 				"it has '%T' instead.", tmpHash) |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	keyContext = append(keyContext, key) | 	keyContext = append(keyContext, key) | ||||||
| 
 | 
 | ||||||
| 	if _, ok := hash[key]; ok { | 	if _, ok := hash[key]; ok { | ||||||
| 		// Typically, if the given key has already been set, then we have
 | 		// Normally redefining keys isn't allowed, but the key could have been
 | ||||||
| 		// to raise an error since duplicate keys are disallowed. However,
 | 		// defined implicitly and it's allowed to be redefined concretely. (See
 | ||||||
| 		// it's possible that a key was previously defined implicitly. In this
 | 		// the `valid/implicit-and-explicit-after.toml` in toml-test)
 | ||||||
| 		// 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
 | 		// But we have to make sure to stop marking it as an implicit. (So that
 | ||||||
| 		// another redefinition provokes an error.)
 | 		// another redefinition provokes an error.)
 | ||||||
| 		//
 | 		//
 | ||||||
| 		// Note that since it has already been defined (as a hash), we don't
 | 		// Note that since it has already been defined (as a hash), we don't
 | ||||||
| 		// want to overwrite it. So our business is done.
 | 		// want to overwrite it. So our business is done.
 | ||||||
|  | 		if p.isArray(keyContext) { | ||||||
|  | 			p.removeImplicit(keyContext) | ||||||
|  | 			hash[key] = value | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 		if p.isImplicit(keyContext) { | 		if p.isImplicit(keyContext) { | ||||||
| 			p.removeImplicit(keyContext) | 			p.removeImplicit(keyContext) | ||||||
| 			return | 			return | ||||||
|  | @ -449,6 +569,7 @@ func (p *parser) setValue(key string, value interface{}) { | ||||||
| 		// key, which is *always* wrong.
 | 		// key, which is *always* wrong.
 | ||||||
| 		p.panicf("Key '%s' has already been defined.", keyContext) | 		p.panicf("Key '%s' has already been defined.", keyContext) | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	hash[key] = value | 	hash[key] = value | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -468,21 +589,15 @@ func (p *parser) setType(key string, typ tomlType) { | ||||||
| 	p.types[keyContext.String()] = typ | 	p.types[keyContext.String()] = typ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // addImplicit sets the given Key as having been created implicitly.
 | // Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and
 | ||||||
| func (p *parser) addImplicit(key Key) { | // "[a.b.c]" (the "a", "b", and "c" hashes are never created explicitly).
 | ||||||
| 	p.implicits[key.String()] = true | func (p *parser) addImplicit(key Key)     { p.implicits[key.String()] = true } | ||||||
| } | func (p *parser) removeImplicit(key Key)  { p.implicits[key.String()] = false } | ||||||
| 
 | func (p *parser) isImplicit(key Key) bool { return p.implicits[key.String()] } | ||||||
| // removeImplicit stops tagging the given key as having been implicitly
 | func (p *parser) isArray(key Key) bool    { return p.types[key.String()] == tomlArray } | ||||||
| // created.
 | func (p *parser) addImplicitContext(key Key) { | ||||||
| func (p *parser) removeImplicit(key Key) { | 	p.addImplicit(key) | ||||||
| 	p.implicits[key.String()] = false | 	p.addContext(key, 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.
 | // current returns the full key name of the current context.
 | ||||||
|  | @ -497,20 +612,54 @@ func (p *parser) current() string { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func stripFirstNewline(s string) string { | func stripFirstNewline(s string) string { | ||||||
| 	if len(s) == 0 || s[0] != '\n' { | 	if len(s) > 0 && s[0] == '\n' { | ||||||
| 		return s | 		return s[1:] | ||||||
| 	} | 	} | ||||||
| 	return s[1:] | 	if len(s) > 1 && s[0] == '\r' && s[1] == '\n' { | ||||||
|  | 		return s[2:] | ||||||
|  | 	} | ||||||
|  | 	return s | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func stripEscapedWhitespace(s string) string { | // Remove newlines inside triple-quoted strings if a line ends with "\".
 | ||||||
| 	esc := strings.Split(s, "\\\n") | func stripEscapedNewlines(s string) string { | ||||||
| 	if len(esc) > 1 { | 	split := strings.Split(s, "\n") | ||||||
| 		for i := 1; i < len(esc); i++ { | 	if len(split) < 1 { | ||||||
| 			esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace) | 		return s | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	escNL := false // Keep track of the last non-blank line was escaped.
 | ||||||
|  | 	for i, line := range split { | ||||||
|  | 		line = strings.TrimRight(line, " \t\r") | ||||||
|  | 
 | ||||||
|  | 		if len(line) == 0 || line[len(line)-1] != '\\' { | ||||||
|  | 			split[i] = strings.TrimRight(split[i], "\r") | ||||||
|  | 			if !escNL && i != len(split)-1 { | ||||||
|  | 				split[i] += "\n" | ||||||
|  | 			} | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		escBS := true | ||||||
|  | 		for j := len(line) - 1; j >= 0 && line[j] == '\\'; j-- { | ||||||
|  | 			escBS = !escBS | ||||||
|  | 		} | ||||||
|  | 		if escNL { | ||||||
|  | 			line = strings.TrimLeft(line, " \t\r") | ||||||
|  | 		} | ||||||
|  | 		escNL = !escBS | ||||||
|  | 
 | ||||||
|  | 		if escBS { | ||||||
|  | 			split[i] += "\n" | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		split[i] = line[:len(line)-1] // Remove \
 | ||||||
|  | 		if len(split)-1 > i { | ||||||
|  | 			split[i+1] = strings.TrimLeft(split[i+1], " \t\r") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return strings.Join(esc, "") | 	return strings.Join(split, "") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *parser) replaceEscapes(str string) string { | func (p *parser) replaceEscapes(str string) string { | ||||||
|  | @ -533,6 +682,9 @@ func (p *parser) replaceEscapes(str string) string { | ||||||
| 		default: | 		default: | ||||||
| 			p.bug("Expected valid escape code after \\, but got %q.", s[r]) | 			p.bug("Expected valid escape code after \\, but got %q.", s[r]) | ||||||
| 			return "" | 			return "" | ||||||
|  | 		case ' ', '\t': | ||||||
|  | 			p.panicf("invalid escape: '\\%c'", s[r]) | ||||||
|  | 			return "" | ||||||
| 		case 'b': | 		case 'b': | ||||||
| 			replaced = append(replaced, rune(0x0008)) | 			replaced = append(replaced, rune(0x0008)) | ||||||
| 			r += 1 | 			r += 1 | ||||||
|  | @ -585,8 +737,3 @@ func (p *parser) asciiEscapeToUnicode(bs []byte) rune { | ||||||
| 	} | 	} | ||||||
| 	return rune(hex) | 	return rune(hex) | ||||||
| } | } | ||||||
| 
 |  | ||||||
| func isStringType(ty itemType) bool { |  | ||||||
| 	return ty == itemString || ty == itemMultilineString || |  | ||||||
| 		ty == itemRawString || ty == itemRawMultilineString |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -1 +0,0 @@ | ||||||
| au BufWritePost *.go silent!make tags > /dev/null 2>&1 |  | ||||||
|  | @ -68,24 +68,3 @@ func (p *parser) typeOfPrimitive(lexItem item) tomlType { | ||||||
| 	p.bug("Cannot infer primitive type of lex item '%s'.", lexItem) | 	p.bug("Cannot infer primitive type of lex item '%s'.", lexItem) | ||||||
| 	panic("unreachable") | 	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,15 @@ | ||||||
|  | # Binaries for programs and plugins | ||||||
|  | *.exe | ||||||
|  | *.exe~ | ||||||
|  | *.dll | ||||||
|  | *.so | ||||||
|  | *.dylib | ||||||
|  | 
 | ||||||
|  | # Test binary, built with `go test -c` | ||||||
|  | *.test | ||||||
|  | 
 | ||||||
|  | # Output of the go coverage tool, specifically when used with LiteIDE | ||||||
|  | *.out | ||||||
|  | 
 | ||||||
|  | # Dependency directories (remove the comment below to include it) | ||||||
|  | # vendor/ | ||||||
|  | @ -0,0 +1,150 @@ | ||||||
|  | # This file contains all available configuration options | ||||||
|  | # with their default values. | ||||||
|  | 
 | ||||||
|  | # options for analysis running | ||||||
|  | run: | ||||||
|  |   # default concurrency is a available CPU number | ||||||
|  |   concurrency: 4 | ||||||
|  | 
 | ||||||
|  |   # timeout for analysis, e.g. 30s, 5m, default is 1m | ||||||
|  |   deadline: 15m | ||||||
|  | 
 | ||||||
|  |   # exit code when at least one issue was found, default is 1 | ||||||
|  |   issues-exit-code: 1 | ||||||
|  | 
 | ||||||
|  |   # include test files or not, default is true | ||||||
|  |   tests: false | ||||||
|  | 
 | ||||||
|  |   # list of build tags, all linters use it. Default is empty list. | ||||||
|  |   #build-tags: | ||||||
|  |   #  - mytag | ||||||
|  | 
 | ||||||
|  |   # which dirs to skip: they won't be analyzed; | ||||||
|  |   # can use regexp here: generated.*, regexp is applied on full path; | ||||||
|  |   # default value is empty list, but next dirs are always skipped independently | ||||||
|  |   # from this option's value: | ||||||
|  |   #   	vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ | ||||||
|  |   skip-dirs: | ||||||
|  |     - /gen$ | ||||||
|  | 
 | ||||||
|  |   # which files to skip: they will be analyzed, but issues from them | ||||||
|  |   # won't be reported. Default value is empty list, but there is | ||||||
|  |   # no need to include all autogenerated files, we confidently recognize | ||||||
|  |   # autogenerated files. If it's not please let us know. | ||||||
|  |   skip-files: | ||||||
|  |     - ".*\\.my\\.go$" | ||||||
|  |     - lib/bad.go | ||||||
|  |     - ".*\\.template\\.go$" | ||||||
|  | 
 | ||||||
|  | # output configuration options | ||||||
|  | output: | ||||||
|  |   # colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number" | ||||||
|  |   format: colored-line-number | ||||||
|  | 
 | ||||||
|  |   # print lines of code with issue, default is true | ||||||
|  |   print-issued-lines: true | ||||||
|  | 
 | ||||||
|  |   # print linter name in the end of issue text, default is true | ||||||
|  |   print-linter-name: true | ||||||
|  | 
 | ||||||
|  | # all available settings of specific linters | ||||||
|  | linters-settings: | ||||||
|  |   errcheck: | ||||||
|  |     # report about not checking of errors in type assetions: `a := b.(MyStruct)`; | ||||||
|  |     # default is false: such cases aren't reported by default. | ||||||
|  |     check-type-assertions: false | ||||||
|  | 
 | ||||||
|  |     # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; | ||||||
|  |     # default is false: such cases aren't reported by default. | ||||||
|  |     check-blank: false | ||||||
|  |   govet: | ||||||
|  |     # report about shadowed variables | ||||||
|  |     check-shadowing: true | ||||||
|  | 
 | ||||||
|  |     # Obtain type information from installed (to $GOPATH/pkg) package files: | ||||||
|  |     # golangci-lint will execute `go install -i` and `go test -i` for analyzed packages | ||||||
|  |     # before analyzing them. | ||||||
|  |     # By default this option is disabled and govet gets type information by loader from source code. | ||||||
|  |     # Loading from source code is slow, but it's done only once for all linters. | ||||||
|  |     # Go-installing of packages first time is much slower than loading them from source code, | ||||||
|  |     # therefore this option is disabled by default. | ||||||
|  |     # But repeated installation is fast in go >= 1.10 because of build caching. | ||||||
|  |     # Enable this option only if all conditions are met: | ||||||
|  |     #  1. you use only "fast" linters (--fast e.g.): no program loading occurs | ||||||
|  |     #  2. you use go >= 1.10 | ||||||
|  |     #  3. you do repeated runs (false for CI) or cache $GOPATH/pkg or `go env GOCACHE` dir in CI. | ||||||
|  |     use-installed-packages: false | ||||||
|  |   golint: | ||||||
|  |     # minimal confidence for issues, default is 0.8 | ||||||
|  |     min-confidence: 0.8 | ||||||
|  |   gofmt: | ||||||
|  |     # simplify code: gofmt with `-s` option, true by default | ||||||
|  |     simplify: true | ||||||
|  |   gocyclo: | ||||||
|  |     # minimal code complexity to report, 30 by default (but we recommend 10-20) | ||||||
|  |     min-complexity: 10 | ||||||
|  |   maligned: | ||||||
|  |     # print struct with more effective memory layout or not, false by default | ||||||
|  |     suggest-new: true | ||||||
|  |   dupl: | ||||||
|  |     # tokens count to trigger issue, 150 by default | ||||||
|  |     threshold: 100 | ||||||
|  |   goconst: | ||||||
|  |     # minimal length of string constant, 3 by default | ||||||
|  |     min-len: 3 | ||||||
|  |     # minimal occurrences count to trigger, 3 by default | ||||||
|  |     min-occurrences: 3 | ||||||
|  |   depguard: | ||||||
|  |     list-type: blacklist | ||||||
|  |     include-go-root: false | ||||||
|  |     packages: | ||||||
|  |       - github.com/davecgh/go-spew/spew | ||||||
|  | 
 | ||||||
|  | linters: | ||||||
|  |   #enable: | ||||||
|  |   #  - staticcheck | ||||||
|  |   #  - unused | ||||||
|  |   #  - gosimple | ||||||
|  |   enable-all: true | ||||||
|  |   disable: | ||||||
|  |     - lll | ||||||
|  |   disable-all: false | ||||||
|  |   #presets: | ||||||
|  |   #  - bugs | ||||||
|  |   #  - unused | ||||||
|  |   fast: false | ||||||
|  | 
 | ||||||
|  | issues: | ||||||
|  |   # List of regexps of issue texts to exclude, empty list by default. | ||||||
|  |   # But independently from this option we use default exclude patterns, | ||||||
|  |   # it can be disabled by `exclude-use-default: false`. To list all | ||||||
|  |   # excluded by default patterns execute `golangci-lint run --help` | ||||||
|  |   exclude: | ||||||
|  |     - "`parseTained` is unused" | ||||||
|  |     - "`parseState` is unused" | ||||||
|  | 
 | ||||||
|  |   # Independently from option `exclude` we use default exclude patterns, | ||||||
|  |   # it can be disabled by this option. To list all | ||||||
|  |   # excluded by default patterns execute `golangci-lint run --help`. | ||||||
|  |   # Default value for this option is false. | ||||||
|  |   exclude-use-default: false | ||||||
|  | 
 | ||||||
|  |   # Maximum issues count per one linter. Set to 0 to disable. Default is 50. | ||||||
|  |   max-per-linter: 0 | ||||||
|  | 
 | ||||||
|  |   # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. | ||||||
|  |   max-same: 0 | ||||||
|  | 
 | ||||||
|  |   # Show only new issues: if there are unstaged changes or untracked files, | ||||||
|  |   # only those changes are analyzed, else only changes in HEAD~ are analyzed. | ||||||
|  |   # It's a super-useful option for integration of golangci-lint into existing | ||||||
|  |   # large codebase. It's not practical to fix all existing issues at the moment | ||||||
|  |   # of integration: much better don't allow issues in new code. | ||||||
|  |   # Default is false. | ||||||
|  |   new: false | ||||||
|  | 
 | ||||||
|  |   # Show only new issues created after git revision `REV` | ||||||
|  |   #new-from-rev: REV | ||||||
|  | 
 | ||||||
|  |   # Show only new issues created in git patch with set file path. | ||||||
|  |   #new-from-patch: path/to/patch/file | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | language: go | ||||||
|  | 
 | ||||||
|  | go: | ||||||
|  |   - "1.13" | ||||||
|  |   - "1.14" | ||||||
|  |   - tip | ||||||
|  | 
 | ||||||
|  | env: | ||||||
|  |   - GO111MODULE=on | ||||||
|  | 
 | ||||||
|  | before_install: | ||||||
|  |   - go get github.com/axw/gocov/gocov | ||||||
|  |   - go get github.com/mattn/goveralls | ||||||
|  |   - go get golang.org/x/tools/cmd/cover | ||||||
|  |   - go get golang.org/x/tools/cmd/goimports | ||||||
|  |   - wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh | ||||||
|  | 
 | ||||||
|  | script: | ||||||
|  |   - test -z "$(goimports -d ./ 2>&1)" | ||||||
|  |   - ./bin/golangci-lint run | ||||||
|  |   - go test -v -race ./... | ||||||
|  | 
 | ||||||
|  | after_success: | ||||||
|  |   - test "$TRAVIS_GO_VERSION" = "1.14" && goveralls -service=travis-ci | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | MIT License | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2020 Djarvur | ||||||
|  | 
 | ||||||
|  | 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,75 @@ | ||||||
|  | = err113 image:https://godoc.org/github.com/Djarvur/go-err113?status.svg["GoDoc",link="http://godoc.org/github.com/Djarvur/go-err113"] image:https://travis-ci.org/Djarvur/go-err113.svg["Build Status",link="https://travis-ci.org/Djarvur/go-err113"] image:https://coveralls.io/repos/Djarvur/go-err113/badge.svg?branch=master&service=github["Coverage Status",link="https://coveralls.io/github/Djarvur/go-err113?branch=master"] | ||||||
|  | Daniel Podolsky | ||||||
|  | :toc: | ||||||
|  | 
 | ||||||
|  | Golang linter to check the errors handling expressions | ||||||
|  | 
 | ||||||
|  | == Details | ||||||
|  | 
 | ||||||
|  | Starting from Go 1.13 the standard `error` type behaviour was changed: one `error` could be derived from another with `fmt.Errorf()` method using `%w` format specifier. | ||||||
|  | 
 | ||||||
|  | So the errors hierarchy could be built for flexible and responsible errors processing. | ||||||
|  | 
 | ||||||
|  | And to make this possible at least two simple rules should be followed: | ||||||
|  | 
 | ||||||
|  | 1. `error` values should not be compared directly but with `errors.Is()` method. | ||||||
|  | 1. `error` should not be created dynamically from scratch but by the wrapping the static (package-level) error. | ||||||
|  | 
 | ||||||
|  | This linter is checking the code for these 2 rules compliance. | ||||||
|  | 
 | ||||||
|  | === Reports | ||||||
|  | 
 | ||||||
|  | So, `err113` reports every `==` and `!=` comparison for exact `error` type variables except comparison to `nil` and `io.EOF`. | ||||||
|  | 
 | ||||||
|  | Also, any call of `errors.New()` and `fmt.Errorf()` methods are reported except the calls used to initialise package-level variables and the `fmt.Errorf()` calls wrapping the other errors. | ||||||
|  | 
 | ||||||
|  | Note: non-standard packages, like `github.com/pkg/errors` are ignored completely. | ||||||
|  | 
 | ||||||
|  | == Install | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | go get -u github.com/Djarvur/go-err113/cmd/err113 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | == Usage | ||||||
|  | 
 | ||||||
|  | Defined by link:https://pkg.go.dev/golang.org/x/tools/go/analysis/singlechecker[singlechecker] package. | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | err113: checks the error handling rules according to the Go 1.13 new error type | ||||||
|  | 
 | ||||||
|  | Usage: err113 [-flag] [package] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Flags: | ||||||
|  |   -V	print version and exit | ||||||
|  |   -all | ||||||
|  |     	no effect (deprecated) | ||||||
|  |   -c int | ||||||
|  |     	display offending line with this many lines of context (default -1) | ||||||
|  |   -cpuprofile string | ||||||
|  |     	write CPU profile to this file | ||||||
|  |   -debug string | ||||||
|  |     	debug flags, any subset of "fpstv" | ||||||
|  |   -fix | ||||||
|  |     	apply all suggested fixes | ||||||
|  |   -flags | ||||||
|  |     	print analyzer flags in JSON | ||||||
|  |   -json | ||||||
|  |     	emit JSON output | ||||||
|  |   -memprofile string | ||||||
|  |     	write memory profile to this file | ||||||
|  |   -source | ||||||
|  |     	no effect (deprecated) | ||||||
|  |   -tags string | ||||||
|  |     	no effect (deprecated) | ||||||
|  |   -trace string | ||||||
|  |     	write trace log to this file | ||||||
|  |   -v	no effect (deprecated) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | == Thanks | ||||||
|  | 
 | ||||||
|  | To link:https://github.com/quasilyte[Iskander (Alex) Sharipov] for the really useful advices. | ||||||
|  | 
 | ||||||
|  | To link:https://github.com/jackwhelpton[Jack Whelpton] for the bugfix provided. | ||||||
|  | @ -0,0 +1,123 @@ | ||||||
|  | package err113 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/token" | ||||||
|  | 	"go/types" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/go/analysis" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func inspectComparision(pass *analysis.Pass, n ast.Node) bool { // nolint: unparam
 | ||||||
|  | 	// check whether the call expression matches time.Now().Sub()
 | ||||||
|  | 	be, ok := n.(*ast.BinaryExpr) | ||||||
|  | 	if !ok { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// check if it is a comparison operation
 | ||||||
|  | 	if be.Op != token.EQL && be.Op != token.NEQ { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !areBothErrors(be.X, be.Y, pass.TypesInfo) { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	oldExpr := render(pass.Fset, be) | ||||||
|  | 
 | ||||||
|  | 	negate := "" | ||||||
|  | 	if be.Op == token.NEQ { | ||||||
|  | 		negate = "!" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	newExpr := fmt.Sprintf("%s%s.Is(%s, %s)", negate, "errors", rawString(be.X), rawString(be.Y)) | ||||||
|  | 
 | ||||||
|  | 	pass.Report( | ||||||
|  | 		analysis.Diagnostic{ | ||||||
|  | 			Pos:     be.Pos(), | ||||||
|  | 			Message: fmt.Sprintf("do not compare errors directly %q, use %q instead", oldExpr, newExpr), | ||||||
|  | 			SuggestedFixes: []analysis.SuggestedFix{ | ||||||
|  | 				{ | ||||||
|  | 					Message: fmt.Sprintf("should replace %q with %q", oldExpr, newExpr), | ||||||
|  | 					TextEdits: []analysis.TextEdit{ | ||||||
|  | 						{ | ||||||
|  | 							Pos:     be.Pos(), | ||||||
|  | 							End:     be.End(), | ||||||
|  | 							NewText: []byte(newExpr), | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isError(v ast.Expr, info *types.Info) bool { | ||||||
|  | 	if intf, ok := info.TypeOf(v).Underlying().(*types.Interface); ok { | ||||||
|  | 		return intf.NumMethods() == 1 && intf.Method(0).FullName() == "(error).Error" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isEOF(ex ast.Expr, info *types.Info) bool { | ||||||
|  | 	se, ok := ex.(*ast.SelectorExpr) | ||||||
|  | 	if !ok || se.Sel.Name != "EOF" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if ep, ok := asImportedName(se.X, info); !ok || ep != "io" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func asImportedName(ex ast.Expr, info *types.Info) (string, bool) { | ||||||
|  | 	ei, ok := ex.(*ast.Ident) | ||||||
|  | 	if !ok { | ||||||
|  | 		return "", false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ep, ok := info.ObjectOf(ei).(*types.PkgName) | ||||||
|  | 	if !ok { | ||||||
|  | 		return "", false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ep.Imported().Path(), true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func areBothErrors(x, y ast.Expr, typesInfo *types.Info) bool { | ||||||
|  | 	// check that both left and right hand side are not nil
 | ||||||
|  | 	if typesInfo.Types[x].IsNil() || typesInfo.Types[y].IsNil() { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// check that both left and right hand side are not io.EOF
 | ||||||
|  | 	if isEOF(x, typesInfo) || isEOF(y, typesInfo) { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// check that both left and right hand side are errors
 | ||||||
|  | 	if !isError(x, typesInfo) && !isError(y, typesInfo) { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func rawString(x ast.Expr) string { | ||||||
|  | 	switch t := x.(type) { | ||||||
|  | 	case *ast.Ident: | ||||||
|  | 		return t.Name | ||||||
|  | 	case *ast.SelectorExpr: | ||||||
|  | 		return fmt.Sprintf("%s.%s", rawString(t.X), t.Sel.Name) | ||||||
|  | 	case *ast.CallExpr: | ||||||
|  | 		return fmt.Sprintf("%s()", rawString(t.Fun)) | ||||||
|  | 	} | ||||||
|  | 	return fmt.Sprintf("%s", x) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,74 @@ | ||||||
|  | package err113 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/types" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/go/analysis" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var methods2check = map[string]map[string]func(*ast.CallExpr, *types.Info) bool{ // nolint: gochecknoglobals
 | ||||||
|  | 	"errors": {"New": justTrue}, | ||||||
|  | 	"fmt":    {"Errorf": checkWrap}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func justTrue(*ast.CallExpr, *types.Info) bool { | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func checkWrap(ce *ast.CallExpr, info *types.Info) bool { | ||||||
|  | 	return !(len(ce.Args) > 0 && strings.Contains(toString(ce.Args[0], info), `%w`)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func inspectDefinition(pass *analysis.Pass, tlds map[*ast.CallExpr]struct{}, n ast.Node) bool { //nolint: unparam
 | ||||||
|  | 	// check whether the call expression matches time.Now().Sub()
 | ||||||
|  | 	ce, ok := n.(*ast.CallExpr) | ||||||
|  | 	if !ok { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if _, ok = tlds[ce]; ok { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fn, ok := ce.Fun.(*ast.SelectorExpr) | ||||||
|  | 	if !ok { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fxName, ok := asImportedName(fn.X, pass.TypesInfo) | ||||||
|  | 	if !ok { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	methods, ok := methods2check[fxName] | ||||||
|  | 	if !ok { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	checkFunc, ok := methods[fn.Sel.Name] | ||||||
|  | 	if !ok { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !checkFunc(ce, pass.TypesInfo) { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pass.Reportf( | ||||||
|  | 		ce.Pos(), | ||||||
|  | 		"do not define dynamic errors, use wrapped static errors instead: %q", | ||||||
|  | 		render(pass.Fset, ce), | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func toString(ex ast.Expr, info *types.Info) string { | ||||||
|  | 	if tv, ok := info.Types[ex]; ok && tv.Value != nil { | ||||||
|  | 		return tv.Value.ExactString() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  | @ -0,0 +1,90 @@ | ||||||
|  | // Package err113 is a Golang linter to check the errors handling expressions
 | ||||||
|  | package err113 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/printer" | ||||||
|  | 	"go/token" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/go/analysis" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // NewAnalyzer creates a new analysis.Analyzer instance tuned to run err113 checks.
 | ||||||
|  | func NewAnalyzer() *analysis.Analyzer { | ||||||
|  | 	return &analysis.Analyzer{ | ||||||
|  | 		Name: "err113", | ||||||
|  | 		Doc:  "checks the error handling rules according to the Go 1.13 new error type", | ||||||
|  | 		Run:  run, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func run(pass *analysis.Pass) (interface{}, error) { | ||||||
|  | 	for _, file := range pass.Files { | ||||||
|  | 		tlds := enumerateFileDecls(file) | ||||||
|  | 
 | ||||||
|  | 		ast.Inspect( | ||||||
|  | 			file, | ||||||
|  | 			func(n ast.Node) bool { | ||||||
|  | 				return inspectComparision(pass, n) && | ||||||
|  | 					inspectDefinition(pass, tlds, n) | ||||||
|  | 			}, | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // render returns the pretty-print of the given node.
 | ||||||
|  | func render(fset *token.FileSet, x interface{}) string { | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 	if err := printer.Fprint(&buf, fset, x); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return buf.String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func enumerateFileDecls(f *ast.File) map[*ast.CallExpr]struct{} { | ||||||
|  | 	res := make(map[*ast.CallExpr]struct{}) | ||||||
|  | 
 | ||||||
|  | 	var ces []*ast.CallExpr // nolint: prealloc
 | ||||||
|  | 
 | ||||||
|  | 	for _, d := range f.Decls { | ||||||
|  | 		ces = append(ces, enumerateDeclVars(d)...) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, ce := range ces { | ||||||
|  | 		res[ce] = struct{}{} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return res | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func enumerateDeclVars(d ast.Decl) (res []*ast.CallExpr) { | ||||||
|  | 	td, ok := d.(*ast.GenDecl) | ||||||
|  | 	if !ok || td.Tok != token.VAR { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, s := range td.Specs { | ||||||
|  | 		res = append(res, enumerateSpecValues(s)...) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return res | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func enumerateSpecValues(s ast.Spec) (res []*ast.CallExpr) { | ||||||
|  | 	vs, ok := s.(*ast.ValueSpec) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, v := range vs.Values { | ||||||
|  | 		if ce, ok := v.(*ast.CallExpr); ok { | ||||||
|  | 			res = append(res, ce) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return res | ||||||
|  | } | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | module github.com/Djarvur/go-err113 | ||||||
|  | 
 | ||||||
|  | go 1.13 | ||||||
|  | 
 | ||||||
|  | require golang.org/x/tools v0.0.0-20200324003944-a576cf524670 | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||||
|  | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||||
|  | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||||
|  | golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= | ||||||
|  | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||||
|  | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||||
|  | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
|  | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
|  | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
|  | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
|  | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||||
|  | golang.org/x/tools v0.0.0-20200324003944-a576cf524670 h1:fW7EP/GZqIvbHessHd1PLca+77TBOsRBqtaybMgXJq8= | ||||||
|  | golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= | ||||||
|  | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= | ||||||
|  | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | @ -0,0 +1,29 @@ | ||||||
|  | language: go | ||||||
|  | 
 | ||||||
|  | go: | ||||||
|  |   - 1.6.x | ||||||
|  |   - 1.7.x | ||||||
|  |   - 1.8.x | ||||||
|  |   - 1.9.x | ||||||
|  |   - 1.10.x | ||||||
|  |   - 1.11.x | ||||||
|  |   - 1.12.x | ||||||
|  |   - tip | ||||||
|  | 
 | ||||||
|  | # Setting sudo access to false will let Travis CI use containers rather than | ||||||
|  | # VMs to run the tests. For more details see: | ||||||
|  | # - http://docs.travis-ci.com/user/workers/container-based-infrastructure/ | ||||||
|  | # - http://docs.travis-ci.com/user/workers/standard-infrastructure/ | ||||||
|  | sudo: false | ||||||
|  | 
 | ||||||
|  | script: | ||||||
|  |   - make setup | ||||||
|  |   - make test | ||||||
|  | 
 | ||||||
|  | notifications: | ||||||
|  |   webhooks: | ||||||
|  |     urls: | ||||||
|  |       - https://webhooks.gitter.im/e/06e3328629952dabe3e0 | ||||||
|  |     on_success: change  # options: [always|never|change] default: always | ||||||
|  |     on_failure: always  # options: [always|never|change] default: always | ||||||
|  |     on_start: never     # options: [always|never|change] default: always | ||||||
|  | @ -0,0 +1,109 @@ | ||||||
|  | # 1.5.0 (2019-09-11) | ||||||
|  | 
 | ||||||
|  | ## Added | ||||||
|  | 
 | ||||||
|  | - #103: Add basic fuzzing for `NewVersion()` (thanks @jesse-c) | ||||||
|  | 
 | ||||||
|  | ## Changed | ||||||
|  | 
 | ||||||
|  | - #82: Clarify wildcard meaning in range constraints and update tests for it (thanks @greysteil) | ||||||
|  | - #83: Clarify caret operator range for pre-1.0.0 dependencies (thanks @greysteil) | ||||||
|  | - #72: Adding docs comment pointing to vert for a cli | ||||||
|  | - #71: Update the docs on pre-release comparator handling | ||||||
|  | - #89: Test with new go versions (thanks @thedevsaddam) | ||||||
|  | - #87: Added $ to ValidPrerelease for better validation (thanks @jeremycarroll) | ||||||
|  | 
 | ||||||
|  | ## Fixed | ||||||
|  | 
 | ||||||
|  | - #78: Fix unchecked error in example code (thanks @ravron) | ||||||
|  | - #70: Fix the handling of pre-releases and the 0.0.0 release edge case | ||||||
|  | - #97: Fixed copyright file for proper display on GitHub | ||||||
|  | - #107: Fix handling prerelease when sorting alphanum and num  | ||||||
|  | - #109: Fixed where Validate sometimes returns wrong message on error | ||||||
|  | 
 | ||||||
|  | # 1.4.2 (2018-04-10) | ||||||
|  | 
 | ||||||
|  | ## Changed | ||||||
|  | - #72: Updated the docs to point to vert for a console appliaction | ||||||
|  | - #71: Update the docs on pre-release comparator handling | ||||||
|  | 
 | ||||||
|  | ## Fixed | ||||||
|  | - #70: Fix the handling of pre-releases and the 0.0.0 release edge case | ||||||
|  | 
 | ||||||
|  | # 1.4.1 (2018-04-02) | ||||||
|  | 
 | ||||||
|  | ## Fixed | ||||||
|  | - Fixed #64: Fix pre-release precedence issue (thanks @uudashr) | ||||||
|  | 
 | ||||||
|  | # 1.4.0 (2017-10-04) | ||||||
|  | 
 | ||||||
|  | ## Changed | ||||||
|  | - #61: Update NewVersion to parse ints with a 64bit int size (thanks @zknill) | ||||||
|  | 
 | ||||||
|  | # 1.3.1 (2017-07-10) | ||||||
|  | 
 | ||||||
|  | ## Fixed | ||||||
|  | - Fixed #57: number comparisons in prerelease sometimes inaccurate | ||||||
|  | 
 | ||||||
|  | # 1.3.0 (2017-05-02) | ||||||
|  | 
 | ||||||
|  | ## Added | ||||||
|  | - #45: Added json (un)marshaling support (thanks @mh-cbon) | ||||||
|  | - Stability marker. See https://masterminds.github.io/stability/ | ||||||
|  | 
 | ||||||
|  | ## Fixed | ||||||
|  | - #51: Fix handling of single digit tilde constraint (thanks @dgodd) | ||||||
|  | 
 | ||||||
|  | ## Changed | ||||||
|  | - #55: The godoc icon moved from png to svg | ||||||
|  | 
 | ||||||
|  | # 1.2.3 (2017-04-03) | ||||||
|  | 
 | ||||||
|  | ## Fixed | ||||||
|  | - #46: Fixed 0.x.x and 0.0.x in constraints being treated as * | ||||||
|  | 
 | ||||||
|  | # Release 1.2.2 (2016-12-13) | ||||||
|  | 
 | ||||||
|  | ## Fixed | ||||||
|  | - #34: Fixed issue where hyphen range was not working with pre-release parsing. | ||||||
|  | 
 | ||||||
|  | # Release 1.2.1 (2016-11-28) | ||||||
|  | 
 | ||||||
|  | ## Fixed | ||||||
|  | - #24: Fixed edge case issue where constraint "> 0" does not handle "0.0.1-alpha" | ||||||
|  |   properly. | ||||||
|  | 
 | ||||||
|  | # Release 1.2.0 (2016-11-04) | ||||||
|  | 
 | ||||||
|  | ## Added | ||||||
|  | - #20: Added MustParse function for versions (thanks @adamreese) | ||||||
|  | - #15: Added increment methods on versions (thanks @mh-cbon) | ||||||
|  | 
 | ||||||
|  | ## Fixed | ||||||
|  | - Issue #21: Per the SemVer spec (section 9) a pre-release is unstable and | ||||||
|  |   might not satisfy the intended compatibility. The change here ignores pre-releases | ||||||
|  |   on constraint checks (e.g., ~ or ^) when a pre-release is not part of the | ||||||
|  |   constraint. For example, `^1.2.3` will ignore pre-releases while | ||||||
|  |   `^1.2.3-alpha` will include them. | ||||||
|  | 
 | ||||||
|  | # Release 1.1.1 (2016-06-30) | ||||||
|  | 
 | ||||||
|  | ## Changed | ||||||
|  | - Issue #9: Speed up version comparison performance (thanks @sdboyer) | ||||||
|  | - Issue #8: Added benchmarks (thanks @sdboyer) | ||||||
|  | - Updated Go Report Card URL to new location | ||||||
|  | - Updated Readme to add code snippet formatting (thanks @mh-cbon) | ||||||
|  | - Updating tagging to v[SemVer] structure for compatibility with other tools. | ||||||
|  | 
 | ||||||
|  | # Release 1.1.0 (2016-03-11) | ||||||
|  | 
 | ||||||
|  | - Issue #2: Implemented validation to provide reasons a versions failed a | ||||||
|  |   constraint. | ||||||
|  | 
 | ||||||
|  | # Release 1.0.1 (2015-12-31) | ||||||
|  | 
 | ||||||
|  | - Fixed #1: * constraint failing on valid versions. | ||||||
|  | 
 | ||||||
|  | # Release 1.0.0 (2015-10-20) | ||||||
|  | 
 | ||||||
|  | - Initial release | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | Copyright (C) 2014-2019, Matt Butcher and Matt Farina | ||||||
|  | 
 | ||||||
|  | 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,36 @@ | ||||||
|  | .PHONY: setup | ||||||
|  | setup: | ||||||
|  | 	go get -u gopkg.in/alecthomas/gometalinter.v1 | ||||||
|  | 	gometalinter.v1 --install | ||||||
|  | 
 | ||||||
|  | .PHONY: test | ||||||
|  | test: validate lint | ||||||
|  | 	@echo "==> Running tests" | ||||||
|  | 	go test -v | ||||||
|  | 
 | ||||||
|  | .PHONY: validate | ||||||
|  | validate: | ||||||
|  | 	@echo "==> Running static validations" | ||||||
|  | 	@gometalinter.v1 \
 | ||||||
|  | 	  --disable-all \
 | ||||||
|  | 	  --enable deadcode \
 | ||||||
|  | 	  --severity deadcode:error \
 | ||||||
|  | 	  --enable gofmt \
 | ||||||
|  | 	  --enable gosimple \
 | ||||||
|  | 	  --enable ineffassign \
 | ||||||
|  | 	  --enable misspell \
 | ||||||
|  | 	  --enable vet \
 | ||||||
|  | 	  --tests \
 | ||||||
|  | 	  --vendor \
 | ||||||
|  | 	  --deadline 60s \
 | ||||||
|  | 	  ./... || exit_code=1 | ||||||
|  | 
 | ||||||
|  | .PHONY: lint | ||||||
|  | lint: | ||||||
|  | 	@echo "==> Running linters" | ||||||
|  | 	@gometalinter.v1 \
 | ||||||
|  | 	  --disable-all \
 | ||||||
|  | 	  --enable golint \
 | ||||||
|  | 	  --vendor \
 | ||||||
|  | 	  --deadline 60s \
 | ||||||
|  | 	  ./... || : | ||||||
|  | @ -0,0 +1,194 @@ | ||||||
|  | # SemVer | ||||||
|  | 
 | ||||||
|  | The `semver` package provides the ability to work with [Semantic Versions](http://semver.org) in Go. Specifically it provides the ability to: | ||||||
|  | 
 | ||||||
|  | * Parse semantic versions | ||||||
|  | * Sort semantic versions | ||||||
|  | * Check if a semantic version fits within a set of constraints | ||||||
|  | * Optionally work with a `v` prefix | ||||||
|  | 
 | ||||||
|  | [](https://masterminds.github.io/stability/active.html) | ||||||
|  | [](https://travis-ci.org/Masterminds/semver) [](https://ci.appveyor.com/project/mattfarina/semver/branch/master) [](https://godoc.org/github.com/Masterminds/semver) [](https://goreportcard.com/report/github.com/Masterminds/semver) | ||||||
|  | 
 | ||||||
|  | If you are looking for a command line tool for version comparisons please see | ||||||
|  | [vert](https://github.com/Masterminds/vert) which uses this library. | ||||||
|  | 
 | ||||||
|  | ## Parsing Semantic Versions | ||||||
|  | 
 | ||||||
|  | To parse a semantic version use the `NewVersion` function. For example, | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  |     v, err := semver.NewVersion("1.2.3-beta.1+build345") | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | If there is an error the version wasn't parseable. The version object has methods | ||||||
|  | to get the parts of the version, compare it to other versions, convert the | ||||||
|  | version back into a string, and get the original string. For more details | ||||||
|  | please see the [documentation](https://godoc.org/github.com/Masterminds/semver). | ||||||
|  | 
 | ||||||
|  | ## Sorting Semantic Versions | ||||||
|  | 
 | ||||||
|  | A set of versions can be sorted using the [`sort`](https://golang.org/pkg/sort/) | ||||||
|  | package from the standard library. For example, | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  |     raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} | ||||||
|  |     vs := make([]*semver.Version, len(raw)) | ||||||
|  | 	for i, r := range raw { | ||||||
|  | 		v, err := semver.NewVersion(r) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Errorf("Error parsing version: %s", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		vs[i] = v | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sort.Sort(semver.Collection(vs)) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Checking Version Constraints | ||||||
|  | 
 | ||||||
|  | Checking a version against version constraints is one of the most featureful | ||||||
|  | parts of the package. | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  |     c, err := semver.NewConstraint(">= 1.2.3") | ||||||
|  |     if err != nil { | ||||||
|  |         // Handle constraint not being parseable. | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     v, _ := semver.NewVersion("1.3") | ||||||
|  |     if err != nil { | ||||||
|  |         // Handle version not being parseable. | ||||||
|  |     } | ||||||
|  |     // Check if the version meets the constraints. The a variable will be true. | ||||||
|  |     a := c.Check(v) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Basic Comparisons | ||||||
|  | 
 | ||||||
|  | There are two elements to the comparisons. First, a comparison string is a list | ||||||
|  | of comma separated and comparisons. These are then separated by || separated or | ||||||
|  | comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a | ||||||
|  | comparison that's greater than or equal to 1.2 and less than 3.0.0 or is | ||||||
|  | greater than or equal to 4.2.3. | ||||||
|  | 
 | ||||||
|  | The basic comparisons are: | ||||||
|  | 
 | ||||||
|  | * `=`: equal (aliased to no operator) | ||||||
|  | * `!=`: not equal | ||||||
|  | * `>`: greater than | ||||||
|  | * `<`: less than | ||||||
|  | * `>=`: greater than or equal to | ||||||
|  | * `<=`: less than or equal to | ||||||
|  | 
 | ||||||
|  | ## Working With Pre-release Versions | ||||||
|  | 
 | ||||||
|  | Pre-releases, for those not familiar with them, are used for software releases | ||||||
|  | prior to stable or generally available releases. Examples of pre-releases include | ||||||
|  | development, alpha, beta, and release candidate releases. A pre-release may be | ||||||
|  | a version such as `1.2.3-beta.1` while the stable release would be `1.2.3`. In the | ||||||
|  | order of precidence, pre-releases come before their associated releases. In this | ||||||
|  | example `1.2.3-beta.1 < 1.2.3`. | ||||||
|  | 
 | ||||||
|  | According to the Semantic Version specification pre-releases may not be | ||||||
|  | API compliant with their release counterpart. It says, | ||||||
|  | 
 | ||||||
|  | > A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version. | ||||||
|  | 
 | ||||||
|  | SemVer comparisons without a pre-release comparator will skip pre-release versions. | ||||||
|  | For example, `>=1.2.3` will skip pre-releases when looking at a list of releases | ||||||
|  | while `>=1.2.3-0` will evaluate and find pre-releases. | ||||||
|  | 
 | ||||||
|  | The reason for the `0` as a pre-release version in the example comparison is | ||||||
|  | because pre-releases can only contain ASCII alphanumerics and hyphens (along with | ||||||
|  | `.` separators), per the spec. Sorting happens in ASCII sort order, again per the spec. The lowest character is a `0` in ASCII sort order (see an [ASCII Table](http://www.asciitable.com/)) | ||||||
|  | 
 | ||||||
|  | Understanding ASCII sort ordering is important because A-Z comes before a-z. That | ||||||
|  | means `>=1.2.3-BETA` will return `1.2.3-alpha`. What you might expect from case | ||||||
|  | sensitivity doesn't apply here. This is due to ASCII sort ordering which is what | ||||||
|  | the spec specifies. | ||||||
|  | 
 | ||||||
|  | ## Hyphen Range Comparisons | ||||||
|  | 
 | ||||||
|  | There are multiple methods to handle ranges and the first is hyphens ranges. | ||||||
|  | These look like: | ||||||
|  | 
 | ||||||
|  | * `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5` | ||||||
|  | * `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5` | ||||||
|  | 
 | ||||||
|  | ## Wildcards In Comparisons | ||||||
|  | 
 | ||||||
|  | The `x`, `X`, and `*` characters can be used as a wildcard character. This works | ||||||
|  | for all comparison operators. When used on the `=` operator it falls | ||||||
|  | back to the pack level comparison (see tilde below). For example, | ||||||
|  | 
 | ||||||
|  | * `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` | ||||||
|  | * `>= 1.2.x` is equivalent to `>= 1.2.0` | ||||||
|  | * `<= 2.x` is equivalent to `< 3` | ||||||
|  | * `*` is equivalent to `>= 0.0.0` | ||||||
|  | 
 | ||||||
|  | ## Tilde Range Comparisons (Patch) | ||||||
|  | 
 | ||||||
|  | The tilde (`~`) comparison operator is for patch level ranges when a minor | ||||||
|  | version is specified and major level changes when the minor number is missing. | ||||||
|  | For example, | ||||||
|  | 
 | ||||||
|  | * `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` | ||||||
|  | * `~1` is equivalent to `>= 1, < 2` | ||||||
|  | * `~2.3` is equivalent to `>= 2.3, < 2.4` | ||||||
|  | * `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` | ||||||
|  | * `~1.x` is equivalent to `>= 1, < 2` | ||||||
|  | 
 | ||||||
|  | ## Caret Range Comparisons (Major) | ||||||
|  | 
 | ||||||
|  | The caret (`^`) comparison operator is for major level changes. This is useful | ||||||
|  | when comparisons of API versions as a major change is API breaking. For example, | ||||||
|  | 
 | ||||||
|  | * `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` | ||||||
|  | * `^0.0.1` is equivalent to `>= 0.0.1, < 1.0.0` | ||||||
|  | * `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` | ||||||
|  | * `^2.3` is equivalent to `>= 2.3, < 3` | ||||||
|  | * `^2.x` is equivalent to `>= 2.0.0, < 3` | ||||||
|  | 
 | ||||||
|  | # Validation | ||||||
|  | 
 | ||||||
|  | In addition to testing a version against a constraint, a version can be validated | ||||||
|  | against a constraint. When validation fails a slice of errors containing why a | ||||||
|  | version didn't meet the constraint is returned. For example, | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  |     c, err := semver.NewConstraint("<= 1.2.3, >= 1.4") | ||||||
|  |     if err != nil { | ||||||
|  |         // Handle constraint not being parseable. | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     v, _ := semver.NewVersion("1.3") | ||||||
|  |     if err != nil { | ||||||
|  |         // Handle version not being parseable. | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Validate a version against a constraint. | ||||||
|  |     a, msgs := c.Validate(v) | ||||||
|  |     // a is false | ||||||
|  |     for _, m := range msgs { | ||||||
|  |         fmt.Println(m) | ||||||
|  | 
 | ||||||
|  |         // Loops over the errors which would read | ||||||
|  |         // "1.3 is greater than 1.2.3" | ||||||
|  |         // "1.3 is less than 1.4" | ||||||
|  |     } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | # Fuzzing | ||||||
|  | 
 | ||||||
|  |  [dvyukov/go-fuzz](https://github.com/dvyukov/go-fuzz) is used for fuzzing. | ||||||
|  | 
 | ||||||
|  | 1. `go-fuzz-build` | ||||||
|  | 2. `go-fuzz -workdir=fuzz` | ||||||
|  | 
 | ||||||
|  | # Contribute | ||||||
|  | 
 | ||||||
|  | If you find an issue or want to contribute please file an [issue](https://github.com/Masterminds/semver/issues) | ||||||
|  | or [create a pull request](https://github.com/Masterminds/semver/pulls). | ||||||
|  | @ -0,0 +1,44 @@ | ||||||
|  | version: build-{build}.{branch} | ||||||
|  | 
 | ||||||
|  | clone_folder: C:\gopath\src\github.com\Masterminds\semver | ||||||
|  | shallow_clone: true | ||||||
|  | 
 | ||||||
|  | environment: | ||||||
|  |   GOPATH: C:\gopath | ||||||
|  | 
 | ||||||
|  | platform: | ||||||
|  |   - x64 | ||||||
|  | 
 | ||||||
|  | install: | ||||||
|  |   - go version | ||||||
|  |   - go env | ||||||
|  |   - go get -u gopkg.in/alecthomas/gometalinter.v1 | ||||||
|  |   - set PATH=%PATH%;%GOPATH%\bin | ||||||
|  |   - gometalinter.v1.exe --install | ||||||
|  | 
 | ||||||
|  | build_script: | ||||||
|  |   - go install -v ./... | ||||||
|  | 
 | ||||||
|  | test_script: | ||||||
|  |   - "gometalinter.v1 \ | ||||||
|  |     --disable-all \ | ||||||
|  |     --enable deadcode \ | ||||||
|  |     --severity deadcode:error \ | ||||||
|  |     --enable gofmt \ | ||||||
|  |     --enable gosimple \ | ||||||
|  |     --enable ineffassign \ | ||||||
|  |     --enable misspell \ | ||||||
|  |     --enable vet \ | ||||||
|  |     --tests \ | ||||||
|  |     --vendor \ | ||||||
|  |     --deadline 60s \ | ||||||
|  |     ./... || exit_code=1" | ||||||
|  |   - "gometalinter.v1 \ | ||||||
|  |     --disable-all \ | ||||||
|  |     --enable golint \ | ||||||
|  |     --vendor \ | ||||||
|  |     --deadline 60s \ | ||||||
|  |     ./... || :" | ||||||
|  |   - go test -v | ||||||
|  | 
 | ||||||
|  | deploy: off | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | package semver | ||||||
|  | 
 | ||||||
|  | // Collection is a collection of Version instances and implements the sort
 | ||||||
|  | // interface. See the sort package for more details.
 | ||||||
|  | // https://golang.org/pkg/sort/
 | ||||||
|  | type Collection []*Version | ||||||
|  | 
 | ||||||
|  | // Len returns the length of a collection. The number of Version instances
 | ||||||
|  | // on the slice.
 | ||||||
|  | func (c Collection) Len() int { | ||||||
|  | 	return len(c) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Less is needed for the sort interface to compare two Version objects on the
 | ||||||
|  | // slice. If checks if one is less than the other.
 | ||||||
|  | func (c Collection) Less(i, j int) bool { | ||||||
|  | 	return c[i].LessThan(c[j]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Swap is needed for the sort interface to replace the Version objects
 | ||||||
|  | // at two different positions in the slice.
 | ||||||
|  | func (c Collection) Swap(i, j int) { | ||||||
|  | 	c[i], c[j] = c[j], c[i] | ||||||
|  | } | ||||||
|  | @ -0,0 +1,423 @@ | ||||||
|  | package semver | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Constraints is one or more constraint that a semantic version can be
 | ||||||
|  | // checked against.
 | ||||||
|  | type Constraints struct { | ||||||
|  | 	constraints [][]*constraint | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewConstraint returns a Constraints instance that a Version instance can
 | ||||||
|  | // be checked against. If there is a parse error it will be returned.
 | ||||||
|  | func NewConstraint(c string) (*Constraints, error) { | ||||||
|  | 
 | ||||||
|  | 	// Rewrite - ranges into a comparison operation.
 | ||||||
|  | 	c = rewriteRange(c) | ||||||
|  | 
 | ||||||
|  | 	ors := strings.Split(c, "||") | ||||||
|  | 	or := make([][]*constraint, len(ors)) | ||||||
|  | 	for k, v := range ors { | ||||||
|  | 		cs := strings.Split(v, ",") | ||||||
|  | 		result := make([]*constraint, len(cs)) | ||||||
|  | 		for i, s := range cs { | ||||||
|  | 			pc, err := parseConstraint(s) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			result[i] = pc | ||||||
|  | 		} | ||||||
|  | 		or[k] = result | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	o := &Constraints{constraints: or} | ||||||
|  | 	return o, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Check tests if a version satisfies the constraints.
 | ||||||
|  | func (cs Constraints) Check(v *Version) bool { | ||||||
|  | 	// loop over the ORs and check the inner ANDs
 | ||||||
|  | 	for _, o := range cs.constraints { | ||||||
|  | 		joy := true | ||||||
|  | 		for _, c := range o { | ||||||
|  | 			if !c.check(v) { | ||||||
|  | 				joy = false | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if joy { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Validate checks if a version satisfies a constraint. If not a slice of
 | ||||||
|  | // reasons for the failure are returned in addition to a bool.
 | ||||||
|  | func (cs Constraints) Validate(v *Version) (bool, []error) { | ||||||
|  | 	// loop over the ORs and check the inner ANDs
 | ||||||
|  | 	var e []error | ||||||
|  | 
 | ||||||
|  | 	// Capture the prerelease message only once. When it happens the first time
 | ||||||
|  | 	// this var is marked
 | ||||||
|  | 	var prerelesase bool | ||||||
|  | 	for _, o := range cs.constraints { | ||||||
|  | 		joy := true | ||||||
|  | 		for _, c := range o { | ||||||
|  | 			// Before running the check handle the case there the version is
 | ||||||
|  | 			// a prerelease and the check is not searching for prereleases.
 | ||||||
|  | 			if c.con.pre == "" && v.pre != "" { | ||||||
|  | 				if !prerelesase { | ||||||
|  | 					em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v) | ||||||
|  | 					e = append(e, em) | ||||||
|  | 					prerelesase = true | ||||||
|  | 				} | ||||||
|  | 				joy = false | ||||||
|  | 
 | ||||||
|  | 			} else { | ||||||
|  | 
 | ||||||
|  | 				if !c.check(v) { | ||||||
|  | 					em := fmt.Errorf(c.msg, v, c.orig) | ||||||
|  | 					e = append(e, em) | ||||||
|  | 					joy = false | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if joy { | ||||||
|  | 			return true, []error{} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false, e | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var constraintOps map[string]cfunc | ||||||
|  | var constraintMsg map[string]string | ||||||
|  | var constraintRegex *regexp.Regexp | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	constraintOps = map[string]cfunc{ | ||||||
|  | 		"":   constraintTildeOrEqual, | ||||||
|  | 		"=":  constraintTildeOrEqual, | ||||||
|  | 		"!=": constraintNotEqual, | ||||||
|  | 		">":  constraintGreaterThan, | ||||||
|  | 		"<":  constraintLessThan, | ||||||
|  | 		">=": constraintGreaterThanEqual, | ||||||
|  | 		"=>": constraintGreaterThanEqual, | ||||||
|  | 		"<=": constraintLessThanEqual, | ||||||
|  | 		"=<": constraintLessThanEqual, | ||||||
|  | 		"~":  constraintTilde, | ||||||
|  | 		"~>": constraintTilde, | ||||||
|  | 		"^":  constraintCaret, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	constraintMsg = map[string]string{ | ||||||
|  | 		"":   "%s is not equal to %s", | ||||||
|  | 		"=":  "%s is not equal to %s", | ||||||
|  | 		"!=": "%s is equal to %s", | ||||||
|  | 		">":  "%s is less than or equal to %s", | ||||||
|  | 		"<":  "%s is greater than or equal to %s", | ||||||
|  | 		">=": "%s is less than %s", | ||||||
|  | 		"=>": "%s is less than %s", | ||||||
|  | 		"<=": "%s is greater than %s", | ||||||
|  | 		"=<": "%s is greater than %s", | ||||||
|  | 		"~":  "%s does not have same major and minor version as %s", | ||||||
|  | 		"~>": "%s does not have same major and minor version as %s", | ||||||
|  | 		"^":  "%s does not have same major version as %s", | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ops := make([]string, 0, len(constraintOps)) | ||||||
|  | 	for k := range constraintOps { | ||||||
|  | 		ops = append(ops, regexp.QuoteMeta(k)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	constraintRegex = regexp.MustCompile(fmt.Sprintf( | ||||||
|  | 		`^\s*(%s)\s*(%s)\s*$`, | ||||||
|  | 		strings.Join(ops, "|"), | ||||||
|  | 		cvRegex)) | ||||||
|  | 
 | ||||||
|  | 	constraintRangeRegex = regexp.MustCompile(fmt.Sprintf( | ||||||
|  | 		`\s*(%s)\s+-\s+(%s)\s*`, | ||||||
|  | 		cvRegex, cvRegex)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // An individual constraint
 | ||||||
|  | type constraint struct { | ||||||
|  | 	// The callback function for the restraint. It performs the logic for
 | ||||||
|  | 	// the constraint.
 | ||||||
|  | 	function cfunc | ||||||
|  | 
 | ||||||
|  | 	msg string | ||||||
|  | 
 | ||||||
|  | 	// The version used in the constraint check. For example, if a constraint
 | ||||||
|  | 	// is '<= 2.0.0' the con a version instance representing 2.0.0.
 | ||||||
|  | 	con *Version | ||||||
|  | 
 | ||||||
|  | 	// The original parsed version (e.g., 4.x from != 4.x)
 | ||||||
|  | 	orig string | ||||||
|  | 
 | ||||||
|  | 	// When an x is used as part of the version (e.g., 1.x)
 | ||||||
|  | 	minorDirty bool | ||||||
|  | 	dirty      bool | ||||||
|  | 	patchDirty bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Check if a version meets the constraint
 | ||||||
|  | func (c *constraint) check(v *Version) bool { | ||||||
|  | 	return c.function(v, c) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type cfunc func(v *Version, c *constraint) bool | ||||||
|  | 
 | ||||||
|  | func parseConstraint(c string) (*constraint, error) { | ||||||
|  | 	m := constraintRegex.FindStringSubmatch(c) | ||||||
|  | 	if m == nil { | ||||||
|  | 		return nil, fmt.Errorf("improper constraint: %s", c) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ver := m[2] | ||||||
|  | 	orig := ver | ||||||
|  | 	minorDirty := false | ||||||
|  | 	patchDirty := false | ||||||
|  | 	dirty := false | ||||||
|  | 	if isX(m[3]) { | ||||||
|  | 		ver = "0.0.0" | ||||||
|  | 		dirty = true | ||||||
|  | 	} else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" { | ||||||
|  | 		minorDirty = true | ||||||
|  | 		dirty = true | ||||||
|  | 		ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) | ||||||
|  | 	} else if isX(strings.TrimPrefix(m[5], ".")) { | ||||||
|  | 		dirty = true | ||||||
|  | 		patchDirty = true | ||||||
|  | 		ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6]) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	con, err := NewVersion(ver) | ||||||
|  | 	if err != nil { | ||||||
|  | 
 | ||||||
|  | 		// The constraintRegex should catch any regex parsing errors. So,
 | ||||||
|  | 		// we should never get here.
 | ||||||
|  | 		return nil, errors.New("constraint Parser Error") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	cs := &constraint{ | ||||||
|  | 		function:   constraintOps[m[1]], | ||||||
|  | 		msg:        constraintMsg[m[1]], | ||||||
|  | 		con:        con, | ||||||
|  | 		orig:       orig, | ||||||
|  | 		minorDirty: minorDirty, | ||||||
|  | 		patchDirty: patchDirty, | ||||||
|  | 		dirty:      dirty, | ||||||
|  | 	} | ||||||
|  | 	return cs, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Constraint functions
 | ||||||
|  | func constraintNotEqual(v *Version, c *constraint) bool { | ||||||
|  | 	if c.dirty { | ||||||
|  | 
 | ||||||
|  | 		// If there is a pre-release on the version but the constraint isn't looking
 | ||||||
|  | 		// for them assume that pre-releases are not compatible. See issue 21 for
 | ||||||
|  | 		// more details.
 | ||||||
|  | 		if v.Prerelease() != "" && c.con.Prerelease() == "" { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if c.con.Major() != v.Major() { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 		if c.con.Minor() != v.Minor() && !c.minorDirty { | ||||||
|  | 			return true | ||||||
|  | 		} else if c.minorDirty { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return !v.Equal(c.con) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func constraintGreaterThan(v *Version, c *constraint) bool { | ||||||
|  | 
 | ||||||
|  | 	// If there is a pre-release on the version but the constraint isn't looking
 | ||||||
|  | 	// for them assume that pre-releases are not compatible. See issue 21 for
 | ||||||
|  | 	// more details.
 | ||||||
|  | 	if v.Prerelease() != "" && c.con.Prerelease() == "" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return v.Compare(c.con) == 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func constraintLessThan(v *Version, c *constraint) bool { | ||||||
|  | 	// If there is a pre-release on the version but the constraint isn't looking
 | ||||||
|  | 	// for them assume that pre-releases are not compatible. See issue 21 for
 | ||||||
|  | 	// more details.
 | ||||||
|  | 	if v.Prerelease() != "" && c.con.Prerelease() == "" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !c.dirty { | ||||||
|  | 		return v.Compare(c.con) < 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if v.Major() > c.con.Major() { | ||||||
|  | 		return false | ||||||
|  | 	} else if v.Minor() > c.con.Minor() && !c.minorDirty { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func constraintGreaterThanEqual(v *Version, c *constraint) bool { | ||||||
|  | 
 | ||||||
|  | 	// If there is a pre-release on the version but the constraint isn't looking
 | ||||||
|  | 	// for them assume that pre-releases are not compatible. See issue 21 for
 | ||||||
|  | 	// more details.
 | ||||||
|  | 	if v.Prerelease() != "" && c.con.Prerelease() == "" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return v.Compare(c.con) >= 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func constraintLessThanEqual(v *Version, c *constraint) bool { | ||||||
|  | 	// If there is a pre-release on the version but the constraint isn't looking
 | ||||||
|  | 	// for them assume that pre-releases are not compatible. See issue 21 for
 | ||||||
|  | 	// more details.
 | ||||||
|  | 	if v.Prerelease() != "" && c.con.Prerelease() == "" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !c.dirty { | ||||||
|  | 		return v.Compare(c.con) <= 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if v.Major() > c.con.Major() { | ||||||
|  | 		return false | ||||||
|  | 	} else if v.Minor() > c.con.Minor() && !c.minorDirty { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ~*, ~>* --> >= 0.0.0 (any)
 | ||||||
|  | // ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0
 | ||||||
|  | // ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0
 | ||||||
|  | // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0
 | ||||||
|  | // ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0
 | ||||||
|  | // ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0
 | ||||||
|  | func constraintTilde(v *Version, c *constraint) bool { | ||||||
|  | 	// If there is a pre-release on the version but the constraint isn't looking
 | ||||||
|  | 	// for them assume that pre-releases are not compatible. See issue 21 for
 | ||||||
|  | 	// more details.
 | ||||||
|  | 	if v.Prerelease() != "" && c.con.Prerelease() == "" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if v.LessThan(c.con) { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// ~0.0.0 is a special case where all constraints are accepted. It's
 | ||||||
|  | 	// equivalent to >= 0.0.0.
 | ||||||
|  | 	if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 && | ||||||
|  | 		!c.minorDirty && !c.patchDirty { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if v.Major() != c.con.Major() { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if v.Minor() != c.con.Minor() && !c.minorDirty { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // When there is a .x (dirty) status it automatically opts in to ~. Otherwise
 | ||||||
|  | // it's a straight =
 | ||||||
|  | func constraintTildeOrEqual(v *Version, c *constraint) bool { | ||||||
|  | 	// If there is a pre-release on the version but the constraint isn't looking
 | ||||||
|  | 	// for them assume that pre-releases are not compatible. See issue 21 for
 | ||||||
|  | 	// more details.
 | ||||||
|  | 	if v.Prerelease() != "" && c.con.Prerelease() == "" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if c.dirty { | ||||||
|  | 		c.msg = constraintMsg["~"] | ||||||
|  | 		return constraintTilde(v, c) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return v.Equal(c.con) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ^* --> (any)
 | ||||||
|  | // ^2, ^2.x, ^2.x.x --> >=2.0.0, <3.0.0
 | ||||||
|  | // ^2.0, ^2.0.x --> >=2.0.0, <3.0.0
 | ||||||
|  | // ^1.2, ^1.2.x --> >=1.2.0, <2.0.0
 | ||||||
|  | // ^1.2.3 --> >=1.2.3, <2.0.0
 | ||||||
|  | // ^1.2.0 --> >=1.2.0, <2.0.0
 | ||||||
|  | func constraintCaret(v *Version, c *constraint) bool { | ||||||
|  | 	// If there is a pre-release on the version but the constraint isn't looking
 | ||||||
|  | 	// for them assume that pre-releases are not compatible. See issue 21 for
 | ||||||
|  | 	// more details.
 | ||||||
|  | 	if v.Prerelease() != "" && c.con.Prerelease() == "" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if v.LessThan(c.con) { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if v.Major() != c.con.Major() { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var constraintRangeRegex *regexp.Regexp | ||||||
|  | 
 | ||||||
|  | const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` + | ||||||
|  | 	`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + | ||||||
|  | 	`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` | ||||||
|  | 
 | ||||||
|  | func isX(x string) bool { | ||||||
|  | 	switch x { | ||||||
|  | 	case "x", "*", "X": | ||||||
|  | 		return true | ||||||
|  | 	default: | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func rewriteRange(i string) string { | ||||||
|  | 	m := constraintRangeRegex.FindAllStringSubmatch(i, -1) | ||||||
|  | 	if m == nil { | ||||||
|  | 		return i | ||||||
|  | 	} | ||||||
|  | 	o := i | ||||||
|  | 	for _, v := range m { | ||||||
|  | 		t := fmt.Sprintf(">= %s, <= %s", v[1], v[11]) | ||||||
|  | 		o = strings.Replace(o, v[0], t, 1) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return o | ||||||
|  | } | ||||||
|  | @ -0,0 +1,115 @@ | ||||||
|  | /* | ||||||
|  | Package semver provides the ability to work with Semantic Versions (http://semver.org) in Go.
 | ||||||
|  | 
 | ||||||
|  | Specifically it provides the ability to: | ||||||
|  | 
 | ||||||
|  |     * Parse semantic versions | ||||||
|  |     * Sort semantic versions | ||||||
|  |     * Check if a semantic version fits within a set of constraints | ||||||
|  |     * Optionally work with a `v` prefix | ||||||
|  | 
 | ||||||
|  | Parsing Semantic Versions | ||||||
|  | 
 | ||||||
|  | To parse a semantic version use the `NewVersion` function. For example, | ||||||
|  | 
 | ||||||
|  |     v, err := semver.NewVersion("1.2.3-beta.1+build345") | ||||||
|  | 
 | ||||||
|  | If there is an error the version wasn't parseable. The version object has methods | ||||||
|  | to get the parts of the version, compare it to other versions, convert the | ||||||
|  | version back into a string, and get the original string. For more details | ||||||
|  | please see the documentation at https://godoc.org/github.com/Masterminds/semver.
 | ||||||
|  | 
 | ||||||
|  | Sorting Semantic Versions | ||||||
|  | 
 | ||||||
|  | A set of versions can be sorted using the `sort` package from the standard library. | ||||||
|  | For example, | ||||||
|  | 
 | ||||||
|  |     raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} | ||||||
|  |     vs := make([]*semver.Version, len(raw)) | ||||||
|  | 	for i, r := range raw { | ||||||
|  | 		v, err := semver.NewVersion(r) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Errorf("Error parsing version: %s", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		vs[i] = v | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sort.Sort(semver.Collection(vs)) | ||||||
|  | 
 | ||||||
|  | Checking Version Constraints | ||||||
|  | 
 | ||||||
|  | Checking a version against version constraints is one of the most featureful | ||||||
|  | parts of the package. | ||||||
|  | 
 | ||||||
|  |     c, err := semver.NewConstraint(">= 1.2.3") | ||||||
|  |     if err != nil { | ||||||
|  |         // Handle constraint not being parseable.
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     v, err := semver.NewVersion("1.3") | ||||||
|  |     if err != nil { | ||||||
|  |         // Handle version not being parseable.
 | ||||||
|  |     } | ||||||
|  |     // Check if the version meets the constraints. The a variable will be true.
 | ||||||
|  |     a := c.Check(v) | ||||||
|  | 
 | ||||||
|  | Basic Comparisons | ||||||
|  | 
 | ||||||
|  | There are two elements to the comparisons. First, a comparison string is a list | ||||||
|  | of comma separated and comparisons. These are then separated by || separated or | ||||||
|  | comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a | ||||||
|  | comparison that's greater than or equal to 1.2 and less than 3.0.0 or is | ||||||
|  | greater than or equal to 4.2.3. | ||||||
|  | 
 | ||||||
|  | The basic comparisons are: | ||||||
|  | 
 | ||||||
|  |     * `=`: equal (aliased to no operator) | ||||||
|  |     * `!=`: not equal | ||||||
|  |     * `>`: greater than | ||||||
|  |     * `<`: less than | ||||||
|  |     * `>=`: greater than or equal to | ||||||
|  |     * `<=`: less than or equal to | ||||||
|  | 
 | ||||||
|  | Hyphen Range Comparisons | ||||||
|  | 
 | ||||||
|  | There are multiple methods to handle ranges and the first is hyphens ranges. | ||||||
|  | These look like: | ||||||
|  | 
 | ||||||
|  |     * `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5` | ||||||
|  |     * `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5` | ||||||
|  | 
 | ||||||
|  | Wildcards In Comparisons | ||||||
|  | 
 | ||||||
|  | The `x`, `X`, and `*` characters can be used as a wildcard character. This works | ||||||
|  | for all comparison operators. When used on the `=` operator it falls | ||||||
|  | back to the pack level comparison (see tilde below). For example, | ||||||
|  | 
 | ||||||
|  |     * `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` | ||||||
|  |     * `>= 1.2.x` is equivalent to `>= 1.2.0` | ||||||
|  |     * `<= 2.x` is equivalent to `<= 3` | ||||||
|  |     * `*` is equivalent to `>= 0.0.0` | ||||||
|  | 
 | ||||||
|  | Tilde Range Comparisons (Patch) | ||||||
|  | 
 | ||||||
|  | The tilde (`~`) comparison operator is for patch level ranges when a minor | ||||||
|  | version is specified and major level changes when the minor number is missing. | ||||||
|  | For example, | ||||||
|  | 
 | ||||||
|  |     * `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` | ||||||
|  |     * `~1` is equivalent to `>= 1, < 2` | ||||||
|  |     * `~2.3` is equivalent to `>= 2.3, < 2.4` | ||||||
|  |     * `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` | ||||||
|  |     * `~1.x` is equivalent to `>= 1, < 2` | ||||||
|  | 
 | ||||||
|  | Caret Range Comparisons (Major) | ||||||
|  | 
 | ||||||
|  | The caret (`^`) comparison operator is for major level changes. This is useful | ||||||
|  | when comparisons of API versions as a major change is API breaking. For example, | ||||||
|  | 
 | ||||||
|  |     * `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` | ||||||
|  |     * `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` | ||||||
|  |     * `^2.3` is equivalent to `>= 2.3, < 3` | ||||||
|  |     * `^2.x` is equivalent to `>= 2.0.0, < 3` | ||||||
|  | */ | ||||||
|  | package semver | ||||||
|  | @ -0,0 +1,425 @@ | ||||||
|  | package semver | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // The compiled version of the regex created at init() is cached here so it
 | ||||||
|  | // only needs to be created once.
 | ||||||
|  | var versionRegex *regexp.Regexp | ||||||
|  | var validPrereleaseRegex *regexp.Regexp | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	// ErrInvalidSemVer is returned a version is found to be invalid when
 | ||||||
|  | 	// being parsed.
 | ||||||
|  | 	ErrInvalidSemVer = errors.New("Invalid Semantic Version") | ||||||
|  | 
 | ||||||
|  | 	// ErrInvalidMetadata is returned when the metadata is an invalid format
 | ||||||
|  | 	ErrInvalidMetadata = errors.New("Invalid Metadata string") | ||||||
|  | 
 | ||||||
|  | 	// ErrInvalidPrerelease is returned when the pre-release is an invalid format
 | ||||||
|  | 	ErrInvalidPrerelease = errors.New("Invalid Prerelease string") | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // SemVerRegex is the regular expression used to parse a semantic version.
 | ||||||
|  | const SemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + | ||||||
|  | 	`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + | ||||||
|  | 	`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` | ||||||
|  | 
 | ||||||
|  | // ValidPrerelease is the regular expression which validates
 | ||||||
|  | // both prerelease and metadata values.
 | ||||||
|  | const ValidPrerelease string = `^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)$` | ||||||
|  | 
 | ||||||
|  | // Version represents a single semantic version.
 | ||||||
|  | type Version struct { | ||||||
|  | 	major, minor, patch int64 | ||||||
|  | 	pre                 string | ||||||
|  | 	metadata            string | ||||||
|  | 	original            string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	versionRegex = regexp.MustCompile("^" + SemVerRegex + "$") | ||||||
|  | 	validPrereleaseRegex = regexp.MustCompile(ValidPrerelease) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewVersion parses a given version and returns an instance of Version or
 | ||||||
|  | // an error if unable to parse the version.
 | ||||||
|  | func NewVersion(v string) (*Version, error) { | ||||||
|  | 	m := versionRegex.FindStringSubmatch(v) | ||||||
|  | 	if m == nil { | ||||||
|  | 		return nil, ErrInvalidSemVer | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sv := &Version{ | ||||||
|  | 		metadata: m[8], | ||||||
|  | 		pre:      m[5], | ||||||
|  | 		original: v, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var temp int64 | ||||||
|  | 	temp, err := strconv.ParseInt(m[1], 10, 64) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("Error parsing version segment: %s", err) | ||||||
|  | 	} | ||||||
|  | 	sv.major = temp | ||||||
|  | 
 | ||||||
|  | 	if m[2] != "" { | ||||||
|  | 		temp, err = strconv.ParseInt(strings.TrimPrefix(m[2], "."), 10, 64) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("Error parsing version segment: %s", err) | ||||||
|  | 		} | ||||||
|  | 		sv.minor = temp | ||||||
|  | 	} else { | ||||||
|  | 		sv.minor = 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if m[3] != "" { | ||||||
|  | 		temp, err = strconv.ParseInt(strings.TrimPrefix(m[3], "."), 10, 64) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("Error parsing version segment: %s", err) | ||||||
|  | 		} | ||||||
|  | 		sv.patch = temp | ||||||
|  | 	} else { | ||||||
|  | 		sv.patch = 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return sv, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MustParse parses a given version and panics on error.
 | ||||||
|  | func MustParse(v string) *Version { | ||||||
|  | 	sv, err := NewVersion(v) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return sv | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // String converts a Version object to a string.
 | ||||||
|  | // Note, if the original version contained a leading v this version will not.
 | ||||||
|  | // See the Original() method to retrieve the original value. Semantic Versions
 | ||||||
|  | // don't contain a leading v per the spec. Instead it's optional on
 | ||||||
|  | // implementation.
 | ||||||
|  | func (v *Version) String() string { | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 
 | ||||||
|  | 	fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch) | ||||||
|  | 	if v.pre != "" { | ||||||
|  | 		fmt.Fprintf(&buf, "-%s", v.pre) | ||||||
|  | 	} | ||||||
|  | 	if v.metadata != "" { | ||||||
|  | 		fmt.Fprintf(&buf, "+%s", v.metadata) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return buf.String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Original returns the original value passed in to be parsed.
 | ||||||
|  | func (v *Version) Original() string { | ||||||
|  | 	return v.original | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Major returns the major version.
 | ||||||
|  | func (v *Version) Major() int64 { | ||||||
|  | 	return v.major | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Minor returns the minor version.
 | ||||||
|  | func (v *Version) Minor() int64 { | ||||||
|  | 	return v.minor | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Patch returns the patch version.
 | ||||||
|  | func (v *Version) Patch() int64 { | ||||||
|  | 	return v.patch | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Prerelease returns the pre-release version.
 | ||||||
|  | func (v *Version) Prerelease() string { | ||||||
|  | 	return v.pre | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Metadata returns the metadata on the version.
 | ||||||
|  | func (v *Version) Metadata() string { | ||||||
|  | 	return v.metadata | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // originalVPrefix returns the original 'v' prefix if any.
 | ||||||
|  | func (v *Version) originalVPrefix() string { | ||||||
|  | 
 | ||||||
|  | 	// Note, only lowercase v is supported as a prefix by the parser.
 | ||||||
|  | 	if v.original != "" && v.original[:1] == "v" { | ||||||
|  | 		return v.original[:1] | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IncPatch produces the next patch version.
 | ||||||
|  | // If the current version does not have prerelease/metadata information,
 | ||||||
|  | // it unsets metadata and prerelease values, increments patch number.
 | ||||||
|  | // If the current version has any of prerelease or metadata information,
 | ||||||
|  | // it unsets both values and keeps curent patch value
 | ||||||
|  | func (v Version) IncPatch() Version { | ||||||
|  | 	vNext := v | ||||||
|  | 	// according to http://semver.org/#spec-item-9
 | ||||||
|  | 	// Pre-release versions have a lower precedence than the associated normal version.
 | ||||||
|  | 	// according to http://semver.org/#spec-item-10
 | ||||||
|  | 	// Build metadata SHOULD be ignored when determining version precedence.
 | ||||||
|  | 	if v.pre != "" { | ||||||
|  | 		vNext.metadata = "" | ||||||
|  | 		vNext.pre = "" | ||||||
|  | 	} else { | ||||||
|  | 		vNext.metadata = "" | ||||||
|  | 		vNext.pre = "" | ||||||
|  | 		vNext.patch = v.patch + 1 | ||||||
|  | 	} | ||||||
|  | 	vNext.original = v.originalVPrefix() + "" + vNext.String() | ||||||
|  | 	return vNext | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IncMinor produces the next minor version.
 | ||||||
|  | // Sets patch to 0.
 | ||||||
|  | // Increments minor number.
 | ||||||
|  | // Unsets metadata.
 | ||||||
|  | // Unsets prerelease status.
 | ||||||
|  | func (v Version) IncMinor() Version { | ||||||
|  | 	vNext := v | ||||||
|  | 	vNext.metadata = "" | ||||||
|  | 	vNext.pre = "" | ||||||
|  | 	vNext.patch = 0 | ||||||
|  | 	vNext.minor = v.minor + 1 | ||||||
|  | 	vNext.original = v.originalVPrefix() + "" + vNext.String() | ||||||
|  | 	return vNext | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // IncMajor produces the next major version.
 | ||||||
|  | // Sets patch to 0.
 | ||||||
|  | // Sets minor to 0.
 | ||||||
|  | // Increments major number.
 | ||||||
|  | // Unsets metadata.
 | ||||||
|  | // Unsets prerelease status.
 | ||||||
|  | func (v Version) IncMajor() Version { | ||||||
|  | 	vNext := v | ||||||
|  | 	vNext.metadata = "" | ||||||
|  | 	vNext.pre = "" | ||||||
|  | 	vNext.patch = 0 | ||||||
|  | 	vNext.minor = 0 | ||||||
|  | 	vNext.major = v.major + 1 | ||||||
|  | 	vNext.original = v.originalVPrefix() + "" + vNext.String() | ||||||
|  | 	return vNext | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetPrerelease defines the prerelease value.
 | ||||||
|  | // Value must not include the required 'hypen' prefix.
 | ||||||
|  | func (v Version) SetPrerelease(prerelease string) (Version, error) { | ||||||
|  | 	vNext := v | ||||||
|  | 	if len(prerelease) > 0 && !validPrereleaseRegex.MatchString(prerelease) { | ||||||
|  | 		return vNext, ErrInvalidPrerelease | ||||||
|  | 	} | ||||||
|  | 	vNext.pre = prerelease | ||||||
|  | 	vNext.original = v.originalVPrefix() + "" + vNext.String() | ||||||
|  | 	return vNext, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetMetadata defines metadata value.
 | ||||||
|  | // Value must not include the required 'plus' prefix.
 | ||||||
|  | func (v Version) SetMetadata(metadata string) (Version, error) { | ||||||
|  | 	vNext := v | ||||||
|  | 	if len(metadata) > 0 && !validPrereleaseRegex.MatchString(metadata) { | ||||||
|  | 		return vNext, ErrInvalidMetadata | ||||||
|  | 	} | ||||||
|  | 	vNext.metadata = metadata | ||||||
|  | 	vNext.original = v.originalVPrefix() + "" + vNext.String() | ||||||
|  | 	return vNext, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // LessThan tests if one version is less than another one.
 | ||||||
|  | func (v *Version) LessThan(o *Version) bool { | ||||||
|  | 	return v.Compare(o) < 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GreaterThan tests if one version is greater than another one.
 | ||||||
|  | func (v *Version) GreaterThan(o *Version) bool { | ||||||
|  | 	return v.Compare(o) > 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Equal tests if two versions are equal to each other.
 | ||||||
|  | // Note, versions can be equal with different metadata since metadata
 | ||||||
|  | // is not considered part of the comparable version.
 | ||||||
|  | func (v *Version) Equal(o *Version) bool { | ||||||
|  | 	return v.Compare(o) == 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Compare compares this version to another one. It returns -1, 0, or 1 if
 | ||||||
|  | // the version smaller, equal, or larger than the other version.
 | ||||||
|  | //
 | ||||||
|  | // Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is
 | ||||||
|  | // lower than the version without a prerelease.
 | ||||||
|  | func (v *Version) Compare(o *Version) int { | ||||||
|  | 	// Compare the major, minor, and patch version for differences. If a
 | ||||||
|  | 	// difference is found return the comparison.
 | ||||||
|  | 	if d := compareSegment(v.Major(), o.Major()); d != 0 { | ||||||
|  | 		return d | ||||||
|  | 	} | ||||||
|  | 	if d := compareSegment(v.Minor(), o.Minor()); d != 0 { | ||||||
|  | 		return d | ||||||
|  | 	} | ||||||
|  | 	if d := compareSegment(v.Patch(), o.Patch()); d != 0 { | ||||||
|  | 		return d | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// At this point the major, minor, and patch versions are the same.
 | ||||||
|  | 	ps := v.pre | ||||||
|  | 	po := o.Prerelease() | ||||||
|  | 
 | ||||||
|  | 	if ps == "" && po == "" { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 	if ps == "" { | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  | 	if po == "" { | ||||||
|  | 		return -1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return comparePrerelease(ps, po) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UnmarshalJSON implements JSON.Unmarshaler interface.
 | ||||||
|  | func (v *Version) UnmarshalJSON(b []byte) error { | ||||||
|  | 	var s string | ||||||
|  | 	if err := json.Unmarshal(b, &s); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	temp, err := NewVersion(s) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	v.major = temp.major | ||||||
|  | 	v.minor = temp.minor | ||||||
|  | 	v.patch = temp.patch | ||||||
|  | 	v.pre = temp.pre | ||||||
|  | 	v.metadata = temp.metadata | ||||||
|  | 	v.original = temp.original | ||||||
|  | 	temp = nil | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalJSON implements JSON.Marshaler interface.
 | ||||||
|  | func (v *Version) MarshalJSON() ([]byte, error) { | ||||||
|  | 	return json.Marshal(v.String()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func compareSegment(v, o int64) int { | ||||||
|  | 	if v < o { | ||||||
|  | 		return -1 | ||||||
|  | 	} | ||||||
|  | 	if v > o { | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func comparePrerelease(v, o string) int { | ||||||
|  | 
 | ||||||
|  | 	// split the prelease versions by their part. The separator, per the spec,
 | ||||||
|  | 	// is a .
 | ||||||
|  | 	sparts := strings.Split(v, ".") | ||||||
|  | 	oparts := strings.Split(o, ".") | ||||||
|  | 
 | ||||||
|  | 	// Find the longer length of the parts to know how many loop iterations to
 | ||||||
|  | 	// go through.
 | ||||||
|  | 	slen := len(sparts) | ||||||
|  | 	olen := len(oparts) | ||||||
|  | 
 | ||||||
|  | 	l := slen | ||||||
|  | 	if olen > slen { | ||||||
|  | 		l = olen | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Iterate over each part of the prereleases to compare the differences.
 | ||||||
|  | 	for i := 0; i < l; i++ { | ||||||
|  | 		// Since the lentgh of the parts can be different we need to create
 | ||||||
|  | 		// a placeholder. This is to avoid out of bounds issues.
 | ||||||
|  | 		stemp := "" | ||||||
|  | 		if i < slen { | ||||||
|  | 			stemp = sparts[i] | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		otemp := "" | ||||||
|  | 		if i < olen { | ||||||
|  | 			otemp = oparts[i] | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		d := comparePrePart(stemp, otemp) | ||||||
|  | 		if d != 0 { | ||||||
|  | 			return d | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Reaching here means two versions are of equal value but have different
 | ||||||
|  | 	// metadata (the part following a +). They are not identical in string form
 | ||||||
|  | 	// but the version comparison finds them to be equal.
 | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func comparePrePart(s, o string) int { | ||||||
|  | 	// Fastpath if they are equal
 | ||||||
|  | 	if s == o { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// When s or o are empty we can use the other in an attempt to determine
 | ||||||
|  | 	// the response.
 | ||||||
|  | 	if s == "" { | ||||||
|  | 		if o != "" { | ||||||
|  | 			return -1 | ||||||
|  | 		} | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if o == "" { | ||||||
|  | 		if s != "" { | ||||||
|  | 			return 1 | ||||||
|  | 		} | ||||||
|  | 		return -1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// When comparing strings "99" is greater than "103". To handle
 | ||||||
|  | 	// cases like this we need to detect numbers and compare them. According
 | ||||||
|  | 	// to the semver spec, numbers are always positive. If there is a - at the
 | ||||||
|  | 	// start like -99 this is to be evaluated as an alphanum. numbers always
 | ||||||
|  | 	// have precedence over alphanum. Parsing as Uints because negative numbers
 | ||||||
|  | 	// are ignored.
 | ||||||
|  | 
 | ||||||
|  | 	oi, n1 := strconv.ParseUint(o, 10, 64) | ||||||
|  | 	si, n2 := strconv.ParseUint(s, 10, 64) | ||||||
|  | 
 | ||||||
|  | 	// The case where both are strings compare the strings
 | ||||||
|  | 	if n1 != nil && n2 != nil { | ||||||
|  | 		if s > o { | ||||||
|  | 			return 1 | ||||||
|  | 		} | ||||||
|  | 		return -1 | ||||||
|  | 	} else if n1 != nil { | ||||||
|  | 		// o is a string and s is a number
 | ||||||
|  | 		return -1 | ||||||
|  | 	} else if n2 != nil { | ||||||
|  | 		// s is a string and o is a number
 | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  | 	// Both are numbers
 | ||||||
|  | 	if si > oi { | ||||||
|  | 		return 1 | ||||||
|  | 	} | ||||||
|  | 	return -1 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,10 @@ | ||||||
|  | // +build gofuzz
 | ||||||
|  | 
 | ||||||
|  | package semver | ||||||
|  | 
 | ||||||
|  | func Fuzz(data []byte) int { | ||||||
|  | 	if _, err := NewVersion(string(data)); err != nil { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 	return 1 | ||||||
|  | } | ||||||
|  | @ -10,3 +10,5 @@ | ||||||
| 
 | 
 | ||||||
| # Output of the go coverage tool, specifically when used with LiteIDE | # Output of the go coverage tool, specifically when used with LiteIDE | ||||||
| *.out | *.out | ||||||
|  | 
 | ||||||
|  | .idea | ||||||
|  |  | ||||||
|  | @ -1,43 +0,0 @@ | ||||||
| # 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 |  | ||||||
|  | @ -1,43 +0,0 @@ | ||||||
| # 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" |  | ||||||
|  | @ -25,22 +25,22 @@ The following is an example configuration file. | ||||||
| ```json | ```json | ||||||
| { | { | ||||||
|   "type": "whitelist", |   "type": "whitelist", | ||||||
|   "packages": [ |   "packages": ["github.com/OpenPeeDeeP/depguard"], | ||||||
|     "github.com/OpenPeeDeeP/depguard" |   "packageErrorMessages": { | ||||||
|   ], |     "github.com/OpenPeeDeeP/depguards": "Please use \"github.com/OpenPeeDeeP/depguard\"," | ||||||
|   "inTests": [ |   }, | ||||||
|     "github.com/stretchr/testify" |   "inTests": ["github.com/stretchr/testify"], | ||||||
|   ], |   "includeGoStdLib": true | ||||||
|   "includeGoRoot": true |  | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| - `type` can be either `whitelist` or `blacklist`. This check is case insensitive. | - `type` can be either `whitelist` or `blacklist`. This check is case insensitive. | ||||||
| If not specified the default is `blacklist`. |   If not specified the default is `blacklist`. | ||||||
| - `packages` is a list of packages for the list type specified. | - `packages` is a list of packages for the list type specified. | ||||||
|  | - `packageErrorMessages` is a mapping from packages to the error message to display | ||||||
| - `inTests` is a list of packages allowed/disallowed only in test files. | - `inTests` is a list of packages allowed/disallowed only in test files. | ||||||
| - Set `includeGoRoot` to true if you want to check the list against standard lib. | - Set `includeGoStdLib` (`includeGoRoot` for backwards compatability) to true if you want to check the list against standard lib. | ||||||
| If not specified the default is false. |   If not specified the default is false. | ||||||
| 
 | 
 | ||||||
| ## Gometalinter | ## Gometalinter | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,10 @@ | ||||||
| package depguard | package depguard | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"go/build" | ||||||
| 	"go/token" | 	"go/token" | ||||||
| 	"os" | 	"io/ioutil" | ||||||
|  | 	"path" | ||||||
| 	"sort" | 	"sort" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
|  | @ -46,8 +48,7 @@ type Depguard struct { | ||||||
| 	prefixTestPackages []string | 	prefixTestPackages []string | ||||||
| 	globTestPackages   []glob.Glob | 	globTestPackages   []glob.Glob | ||||||
| 
 | 
 | ||||||
| 	rootChecker *RootChecker | 	prefixRoot []string | ||||||
| 	cwd         string |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Run checks for dependencies given the program and validates them against
 | // Run checks for dependencies given the program and validates them against
 | ||||||
|  | @ -86,18 +87,6 @@ func (dg *Depguard) Run(config *loader.Config, prog *loader.Program) ([]*Issue, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dg *Depguard) initialize(config *loader.Config, prog *loader.Program) error { | 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 |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	dg.rootChecker = NewRootChecker(config.Build) |  | ||||||
| 
 |  | ||||||
| 	// parse ordinary guarded packages
 | 	// parse ordinary guarded packages
 | ||||||
| 	for _, pkg := range dg.Packages { | 	for _, pkg := range dg.Packages { | ||||||
| 		if strings.ContainsAny(pkg, "!?*[]{}") { | 		if strings.ContainsAny(pkg, "!?*[]{}") { | ||||||
|  | @ -130,6 +119,14 @@ func (dg *Depguard) initialize(config *loader.Config, prog *loader.Program) erro | ||||||
| 	// Sort the test packages so we can have a faster search in the array
 | 	// Sort the test packages so we can have a faster search in the array
 | ||||||
| 	sort.Strings(dg.prefixTestPackages) | 	sort.Strings(dg.prefixTestPackages) | ||||||
| 
 | 
 | ||||||
|  | 	if !dg.IncludeGoRoot { | ||||||
|  | 		var err error | ||||||
|  | 		dg.prefixRoot, err = listRootPrefixs(config.Build) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -143,14 +140,8 @@ func (dg *Depguard) createImportMap(prog *loader.Program) (map[string][]token.Po | ||||||
| 			// This will filter out GoRoot depending on the Depguard.IncludeGoRoot
 | 			// This will filter out GoRoot depending on the Depguard.IncludeGoRoot
 | ||||||
| 			for _, fileImport := range file.Imports { | 			for _, fileImport := range file.Imports { | ||||||
| 				fileImportPath := cleanBasicLitString(fileImport.Path.Value) | 				fileImportPath := cleanBasicLitString(fileImport.Path.Value) | ||||||
| 				if !dg.IncludeGoRoot { | 				if !dg.IncludeGoRoot && dg.isRoot(fileImportPath) { | ||||||
| 					isRoot, err := dg.rootChecker.IsRoot(fileImportPath, dg.cwd) | 					continue | ||||||
| 					if err != nil { |  | ||||||
| 						return nil, err |  | ||||||
| 					} |  | ||||||
| 					if isRoot { |  | ||||||
| 						continue |  | ||||||
| 					} |  | ||||||
| 				} | 				} | ||||||
| 				position := prog.Fset.Position(fileImport.Pos()) | 				position := prog.Fset.Position(fileImport.Pos()) | ||||||
| 				positions, found := importMap[fileImportPath] | 				positions, found := importMap[fileImportPath] | ||||||
|  | @ -167,14 +158,14 @@ func (dg *Depguard) createImportMap(prog *loader.Program) (map[string][]token.Po | ||||||
| 	return importMap, nil | 	return importMap, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dg *Depguard) pkgInList(pkg string, prefixList []string, globList []glob.Glob) bool { | func pkgInList(pkg string, prefixList []string, globList []glob.Glob) bool { | ||||||
| 	if dg.pkgInPrefixList(pkg, prefixList) { | 	if pkgInPrefixList(pkg, prefixList) { | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 	return dg.pkgInGlobList(pkg, globList) | 	return pkgInGlobList(pkg, globList) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dg *Depguard) pkgInPrefixList(pkg string, prefixList []string) bool { | func pkgInPrefixList(pkg string, prefixList []string) bool { | ||||||
| 	// Idx represents where in the package slice the passed in package would go
 | 	// 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.
 | 	// when sorted. -1 Just means that it would be at the very front of the slice.
 | ||||||
| 	idx := sort.Search(len(prefixList), func(i int) bool { | 	idx := sort.Search(len(prefixList), func(i int) bool { | ||||||
|  | @ -188,7 +179,7 @@ func (dg *Depguard) pkgInPrefixList(pkg string, prefixList []string) bool { | ||||||
| 	return strings.HasPrefix(pkg, prefixList[idx]) | 	return strings.HasPrefix(pkg, prefixList[idx]) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dg *Depguard) pkgInGlobList(pkg string, globList []glob.Glob) bool { | func pkgInGlobList(pkg string, globList []glob.Glob) bool { | ||||||
| 	for _, g := range globList { | 	for _, g := range globList { | ||||||
| 		if g.Match(pkg) { | 		if g.Match(pkg) { | ||||||
| 			return true | 			return true | ||||||
|  | @ -201,9 +192,50 @@ func (dg *Depguard) pkgInGlobList(pkg string, globList []glob.Glob) bool { | ||||||
| //   y   |           |     x
 | //   y   |           |     x
 | ||||||
| //   n   |     x     |
 | //   n   |     x     |
 | ||||||
| func (dg *Depguard) flagIt(pkg string, prefixList []string, globList []glob.Glob) bool { | func (dg *Depguard) flagIt(pkg string, prefixList []string, globList []glob.Glob) bool { | ||||||
| 	return dg.pkgInList(pkg, prefixList, globList) == (dg.ListType == LTBlacklist) | 	return pkgInList(pkg, prefixList, globList) == (dg.ListType == LTBlacklist) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func cleanBasicLitString(value string) string { | func cleanBasicLitString(value string) string { | ||||||
| 	return strings.Trim(value, "\"\\") | 	return strings.Trim(value, "\"\\") | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // We can do this as all imports that are not root are either prefixed with a domain
 | ||||||
|  | // or prefixed with `./` or `/` to dictate it is a local file reference
 | ||||||
|  | func listRootPrefixs(buildCtx *build.Context) ([]string, error) { | ||||||
|  | 	if buildCtx == nil { | ||||||
|  | 		buildCtx = &build.Default | ||||||
|  | 	} | ||||||
|  | 	root := path.Join(buildCtx.GOROOT, "src") | ||||||
|  | 	fs, err := ioutil.ReadDir(root) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	var pkgPrefix []string | ||||||
|  | 	for _, f := range fs { | ||||||
|  | 		if !f.IsDir() { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		pkgPrefix = append(pkgPrefix, f.Name()) | ||||||
|  | 	} | ||||||
|  | 	return pkgPrefix, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (dg *Depguard) isRoot(importPath 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.prefixRoot), func(i int) bool { | ||||||
|  | 		return dg.prefixRoot[i] > importPath | ||||||
|  | 	}) - 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 | ||||||
|  | 	} | ||||||
|  | 	// if it is prefixed by a root prefix we need to check if it is an exact match
 | ||||||
|  | 	// or prefix with `/` as this could return false posative if the domain was
 | ||||||
|  | 	// `archive.com` for example as `archive` is a go root package.
 | ||||||
|  | 	if strings.HasPrefix(importPath, dg.prefixRoot[idx]) { | ||||||
|  | 		return strings.HasPrefix(importPath, dg.prefixRoot[idx]+"/") || importPath == dg.prefixRoot[idx] | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | module github.com/OpenPeeDeeP/depguard | ||||||
|  | 
 | ||||||
|  | go 1.13 | ||||||
|  | 
 | ||||||
|  | require ( | ||||||
|  | 	github.com/gobwas/glob v0.2.3 | ||||||
|  | 	github.com/kisielk/gotool v1.0.0 | ||||||
|  | 	golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b | ||||||
|  | ) | ||||||
|  | @ -0,0 +1,6 @@ | ||||||
|  | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= | ||||||
|  | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= | ||||||
|  | github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= | ||||||
|  | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | ||||||
|  | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b h1:7tibmaEqrQYA+q6ri7NQjuxqSwechjtDHKq6/e85S38= | ||||||
|  | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
|  | @ -1,52 +0,0 @@ | ||||||
| package depguard |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"go/build" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // RootChecker checks if import paths point to root packages.
 |  | ||||||
| type RootChecker struct { |  | ||||||
| 	buildCtx *build.Context |  | ||||||
| 	cache    map[string]bool |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // NewRootChecker creates a new RootChecker instance using the build.Context
 |  | ||||||
| // given, or build.Default.
 |  | ||||||
| func NewRootChecker(buildCtx *build.Context) *RootChecker { |  | ||||||
| 	// Use the &build.Default if build.Context is not specified
 |  | ||||||
| 	ctx := buildCtx |  | ||||||
| 	if ctx == nil { |  | ||||||
| 		ctx = &build.Default |  | ||||||
| 	} |  | ||||||
| 	return &RootChecker{ |  | ||||||
| 		buildCtx: ctx, |  | ||||||
| 		cache:    make(map[string]bool, 0), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // IsRoot checks if the given import path (imported from sourceDir)
 |  | ||||||
| // points to a a root package. Subsequent calls with the same arguments
 |  | ||||||
| // are cached. This is not thread-safe.
 |  | ||||||
| func (rc *RootChecker) IsRoot(path, sourceDir string) (bool, error) { |  | ||||||
| 	key := path + ":::" + sourceDir |  | ||||||
| 	isRoot, ok := rc.cache[key] |  | ||||||
| 	if ok { |  | ||||||
| 		return isRoot, nil |  | ||||||
| 	} |  | ||||||
| 	isRoot, err := rc.calcIsRoot(path, sourceDir) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return false, err |  | ||||||
| 	} |  | ||||||
| 	rc.cache[key] = isRoot |  | ||||||
| 	return isRoot, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // calcIsRoot performs the call to the build context to check if
 |  | ||||||
| // the import path points to a root package.
 |  | ||||||
| func (rc *RootChecker) calcIsRoot(path, sourceDir string) (bool, error) { |  | ||||||
| 	pkg, err := rc.buildCtx.Import(path, sourceDir, build.FindOnly) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return false, err |  | ||||||
| 	} |  | ||||||
| 	return pkg.Goroot, nil |  | ||||||
| } |  | ||||||
							
								
								
									
										267
									
								
								tests/tools/vendor/github.com/alexkohler/prealloc/pkg/prealloc.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										267
									
								
								tests/tools/vendor/github.com/alexkohler/prealloc/pkg/prealloc.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,267 @@ | ||||||
|  | package pkg | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/token" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type sliceDeclaration struct { | ||||||
|  | 	name string | ||||||
|  | 	// sType string
 | ||||||
|  | 	genD *ast.GenDecl | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type returnsVisitor struct { | ||||||
|  | 	// flags
 | ||||||
|  | 	simple            bool | ||||||
|  | 	includeRangeLoops bool | ||||||
|  | 	includeForLoops   bool | ||||||
|  | 	// visitor fields
 | ||||||
|  | 	sliceDeclarations   []*sliceDeclaration | ||||||
|  | 	preallocHints       []Hint | ||||||
|  | 	returnsInsideOfLoop bool | ||||||
|  | 	arrayTypes          []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func Check(files []*ast.File, simple, includeRangeLoops, includeForLoops bool) []Hint { | ||||||
|  | 	hints := []Hint{} | ||||||
|  | 	for _, f := range files { | ||||||
|  | 		retVis := &returnsVisitor{ | ||||||
|  | 			simple:            simple, | ||||||
|  | 			includeRangeLoops: includeRangeLoops, | ||||||
|  | 			includeForLoops:   includeForLoops, | ||||||
|  | 		} | ||||||
|  | 		ast.Walk(retVis, f) | ||||||
|  | 		// if simple is true, then we actually have to check if we had returns
 | ||||||
|  | 		// inside of our loop. Otherwise, we can just report all messages.
 | ||||||
|  | 		if !retVis.simple || !retVis.returnsInsideOfLoop { | ||||||
|  | 			hints = append(hints, retVis.preallocHints...) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return hints | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func contains(slice []string, item string) bool { | ||||||
|  | 	for _, s := range slice { | ||||||
|  | 		if s == item { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *returnsVisitor) Visit(node ast.Node) ast.Visitor { | ||||||
|  | 
 | ||||||
|  | 	v.sliceDeclarations = nil | ||||||
|  | 	v.returnsInsideOfLoop = false | ||||||
|  | 
 | ||||||
|  | 	switch n := node.(type) { | ||||||
|  | 	case *ast.TypeSpec: | ||||||
|  | 		if _, ok := n.Type.(*ast.ArrayType); ok { | ||||||
|  | 			if n.Name != nil { | ||||||
|  | 				v.arrayTypes = append(v.arrayTypes, n.Name.Name) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	case *ast.FuncDecl: | ||||||
|  | 		if n.Body != nil { | ||||||
|  | 			for _, stmt := range n.Body.List { | ||||||
|  | 				switch s := stmt.(type) { | ||||||
|  | 				// Find non pre-allocated slices
 | ||||||
|  | 				case *ast.DeclStmt: | ||||||
|  | 					genD, ok := s.Decl.(*ast.GenDecl) | ||||||
|  | 					if !ok { | ||||||
|  | 						continue | ||||||
|  | 					} | ||||||
|  | 					if genD.Tok == token.TYPE { | ||||||
|  | 						for _, spec := range genD.Specs { | ||||||
|  | 							tSpec, ok := spec.(*ast.TypeSpec) | ||||||
|  | 							if !ok { | ||||||
|  | 								continue | ||||||
|  | 							} | ||||||
|  | 
 | ||||||
|  | 							if _, ok := tSpec.Type.(*ast.ArrayType); ok { | ||||||
|  | 								if tSpec.Name != nil { | ||||||
|  | 									v.arrayTypes = append(v.arrayTypes, tSpec.Name.Name) | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} else if genD.Tok == token.VAR { | ||||||
|  | 						for _, spec := range genD.Specs { | ||||||
|  | 							vSpec, ok := spec.(*ast.ValueSpec) | ||||||
|  | 							if !ok { | ||||||
|  | 								continue | ||||||
|  | 							} | ||||||
|  | 							var isArrType bool | ||||||
|  | 							switch val := vSpec.Type.(type) { | ||||||
|  | 							case *ast.ArrayType: | ||||||
|  | 								isArrType = true | ||||||
|  | 							case *ast.Ident: | ||||||
|  | 								isArrType = contains(v.arrayTypes, val.Name) | ||||||
|  | 							} | ||||||
|  | 							if isArrType { | ||||||
|  | 								if vSpec.Names != nil { | ||||||
|  | 									/*atID, ok := arrayType.Elt.(*ast.Ident) | ||||||
|  | 									if !ok { | ||||||
|  | 										continue | ||||||
|  | 									}*/ | ||||||
|  | 
 | ||||||
|  | 									// We should handle multiple slices declared on same line e.g. var mySlice1, mySlice2 []uint32
 | ||||||
|  | 									for _, vName := range vSpec.Names { | ||||||
|  | 										v.sliceDeclarations = append(v.sliceDeclarations, &sliceDeclaration{name: vName.Name /*sType: atID.Name,*/, genD: genD}) | ||||||
|  | 									} | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 				case *ast.RangeStmt: | ||||||
|  | 					if v.includeRangeLoops { | ||||||
|  | 						if len(v.sliceDeclarations) == 0 { | ||||||
|  | 							continue | ||||||
|  | 						} | ||||||
|  | 						// Check the value being ranged over and ensure it's not a channel (we cannot offer any recommendations on channel ranges).
 | ||||||
|  | 						rangeIdent, ok := s.X.(*ast.Ident) | ||||||
|  | 						if ok && rangeIdent.Obj != nil { | ||||||
|  | 							valueSpec, ok := rangeIdent.Obj.Decl.(*ast.ValueSpec) | ||||||
|  | 							if ok { | ||||||
|  | 								if _, rangeTargetIsChannel := valueSpec.Type.(*ast.ChanType); rangeTargetIsChannel { | ||||||
|  | 									continue | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 						if s.Body != nil { | ||||||
|  | 							v.handleLoops(s.Body) | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 				case *ast.ForStmt: | ||||||
|  | 					if v.includeForLoops { | ||||||
|  | 						if len(v.sliceDeclarations) == 0 { | ||||||
|  | 							continue | ||||||
|  | 						} | ||||||
|  | 						if s.Body != nil { | ||||||
|  | 							v.handleLoops(s.Body) | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 				default: | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return v | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // handleLoops is a helper function to share the logic required for both *ast.RangeLoops and *ast.ForLoops
 | ||||||
|  | func (v *returnsVisitor) handleLoops(blockStmt *ast.BlockStmt) { | ||||||
|  | 
 | ||||||
|  | 	for _, stmt := range blockStmt.List { | ||||||
|  | 		switch bodyStmt := stmt.(type) { | ||||||
|  | 		case *ast.AssignStmt: | ||||||
|  | 			asgnStmt := bodyStmt | ||||||
|  | 			for index, expr := range asgnStmt.Rhs { | ||||||
|  | 				if index >= len(asgnStmt.Lhs) { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				lhsIdent, ok := asgnStmt.Lhs[index].(*ast.Ident) | ||||||
|  | 				if !ok { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				callExpr, ok := expr.(*ast.CallExpr) | ||||||
|  | 				if !ok { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				rhsFuncIdent, ok := callExpr.Fun.(*ast.Ident) | ||||||
|  | 				if !ok { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if rhsFuncIdent.Name != "append" { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				// e.g., `x = append(x)`
 | ||||||
|  | 				// Pointless, but pre-allocation will not help.
 | ||||||
|  | 				if len(callExpr.Args) < 2 { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				rhsIdent, ok := callExpr.Args[0].(*ast.Ident) | ||||||
|  | 				if !ok { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				// e.g., `x = append(y, a)`
 | ||||||
|  | 				// This is weird (and maybe a logic error),
 | ||||||
|  | 				// but we cannot recommend pre-allocation.
 | ||||||
|  | 				if lhsIdent.Name != rhsIdent.Name { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				// e.g., `x = append(x, y...)`
 | ||||||
|  | 				// we should ignore this. Pre-allocating in this case
 | ||||||
|  | 				// is confusing, and is not possible in general.
 | ||||||
|  | 				if callExpr.Ellipsis.IsValid() { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				for _, sliceDecl := range v.sliceDeclarations { | ||||||
|  | 					if sliceDecl.name == lhsIdent.Name { | ||||||
|  | 						// This is a potential mark, we just need to make sure there are no returns/continues in the
 | ||||||
|  | 						// range loop.
 | ||||||
|  | 						// now we just need to grab whatever we're ranging over
 | ||||||
|  | 						/*sxIdent, ok := s.X.(*ast.Ident) | ||||||
|  | 						if !ok { | ||||||
|  | 							continue | ||||||
|  | 						}*/ | ||||||
|  | 
 | ||||||
|  | 						v.preallocHints = append(v.preallocHints, Hint{ | ||||||
|  | 							Pos:               sliceDecl.genD.Pos(), | ||||||
|  | 							DeclaredSliceName: sliceDecl.name, | ||||||
|  | 						}) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		case *ast.IfStmt: | ||||||
|  | 			ifStmt := bodyStmt | ||||||
|  | 			if ifStmt.Body != nil { | ||||||
|  | 				for _, ifBodyStmt := range ifStmt.Body.List { | ||||||
|  | 					// TODO should probably handle embedded ifs here
 | ||||||
|  | 					switch /*ift :=*/ ifBodyStmt.(type) { | ||||||
|  | 					case *ast.BranchStmt, *ast.ReturnStmt: | ||||||
|  | 						v.returnsInsideOfLoop = true | ||||||
|  | 					default: | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 		default: | ||||||
|  | 
 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Hint stores the information about an occurrence of a slice that could be
 | ||||||
|  | // preallocated.
 | ||||||
|  | type Hint struct { | ||||||
|  | 	Pos               token.Pos | ||||||
|  | 	DeclaredSliceName string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (h Hint) String() string { | ||||||
|  | 	return fmt.Sprintf("%v: Consider preallocating %v", h.Pos, h.DeclaredSliceName) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (h Hint) StringFromFS(f *token.FileSet) string { | ||||||
|  | 	file := f.File(h.Pos) | ||||||
|  | 	lineNumber := file.Position(h.Pos).Line | ||||||
|  | 
 | ||||||
|  | 	return fmt.Sprintf("%v:%v Consider preallocating %v", file.Name(), lineNumber, h.DeclaredSliceName) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | Copyright 2019 Andrew Shannon Brown | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
							
								
								
									
										45
									
								
								tests/tools/vendor/github.com/ashanbrown/forbidigo/forbidigo/config_options.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										45
									
								
								tests/tools/vendor/github.com/ashanbrown/forbidigo/forbidigo/config_options.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,45 @@ | ||||||
|  | package forbidigo | ||||||
|  | 
 | ||||||
|  | // Code generated by github.com/launchdarkly/go-options.  DO NOT EDIT.
 | ||||||
|  | 
 | ||||||
|  | type ApplyOptionFunc func(c *config) error | ||||||
|  | 
 | ||||||
|  | func (f ApplyOptionFunc) apply(c *config) error { | ||||||
|  | 	return f(c) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newConfig(options ...Option) (config, error) { | ||||||
|  | 	var c config | ||||||
|  | 	err := applyConfigOptions(&c, options...) | ||||||
|  | 	return c, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func applyConfigOptions(c *config, options ...Option) error { | ||||||
|  | 	c.ExcludeGodocExamples = true | ||||||
|  | 	for _, o := range options { | ||||||
|  | 		if err := o.apply(c); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Option interface { | ||||||
|  | 	apply(*config) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // OptionExcludeGodocExamples don't check inside Godoc examples (see https://blog.golang.org/examples)
 | ||||||
|  | func OptionExcludeGodocExamples(o bool) ApplyOptionFunc { | ||||||
|  | 	return func(c *config) error { | ||||||
|  | 		c.ExcludeGodocExamples = o | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // OptionIgnorePermitDirectives don't check for `permit` directives(for example, in favor of `nolint`)
 | ||||||
|  | func OptionIgnorePermitDirectives(o bool) ApplyOptionFunc { | ||||||
|  | 	return func(c *config) error { | ||||||
|  | 		c.IgnorePermitDirectives = o | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										193
									
								
								tests/tools/vendor/github.com/ashanbrown/forbidigo/forbidigo/forbidigo.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										193
									
								
								tests/tools/vendor/github.com/ashanbrown/forbidigo/forbidigo/forbidigo.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,193 @@ | ||||||
|  | // forbidigo provides a linter for forbidding the use of specific identifiers
 | ||||||
|  | package forbidigo | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/printer" | ||||||
|  | 	"go/token" | ||||||
|  | 	"log" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/pkg/errors" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type Issue interface { | ||||||
|  | 	Details() string | ||||||
|  | 	Position() token.Position | ||||||
|  | 	String() string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type UsedIssue struct { | ||||||
|  | 	identifier string | ||||||
|  | 	pattern    string | ||||||
|  | 	position   token.Position | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a UsedIssue) Details() string { | ||||||
|  | 	return fmt.Sprintf("use of `%s` forbidden by pattern `%s`", a.identifier, a.pattern) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a UsedIssue) Position() token.Position { | ||||||
|  | 	return a.position | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a UsedIssue) String() string { return toString(a) } | ||||||
|  | 
 | ||||||
|  | func toString(i Issue) string { | ||||||
|  | 	return fmt.Sprintf("%s at %s", i.Details(), i.Position()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Linter struct { | ||||||
|  | 	cfg      config | ||||||
|  | 	patterns []*regexp.Regexp | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func DefaultPatterns() []string { | ||||||
|  | 	return []string{`^(fmt\.Print(|f|ln)|print|println)$`} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | //go:generate go-options config
 | ||||||
|  | type config struct { | ||||||
|  | 	// don't check inside Godoc examples (see https://blog.golang.org/examples)
 | ||||||
|  | 	ExcludeGodocExamples   bool `options:",true"` | ||||||
|  | 	IgnorePermitDirectives bool // don't check for `permit` directives(for example, in favor of `nolint`)
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewLinter(patterns []string, options ...Option) (*Linter, error) { | ||||||
|  | 	cfg, err := newConfig(options...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, errors.Wrapf(err, "failed to process options") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(patterns) == 0 { | ||||||
|  | 		patterns = DefaultPatterns() | ||||||
|  | 	} | ||||||
|  | 	compiledPatterns := make([]*regexp.Regexp, 0, len(patterns)) | ||||||
|  | 	for _, p := range patterns { | ||||||
|  | 		re, err := regexp.Compile(p) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("unable to compile pattern `%s`: %s", p, err) | ||||||
|  | 		} | ||||||
|  | 		compiledPatterns = append(compiledPatterns, re) | ||||||
|  | 	} | ||||||
|  | 	return &Linter{ | ||||||
|  | 		cfg:      cfg, | ||||||
|  | 		patterns: compiledPatterns, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type visitor struct { | ||||||
|  | 	cfg        config | ||||||
|  | 	isTestFile bool // godoc only runs on test files
 | ||||||
|  | 
 | ||||||
|  | 	linter   *Linter | ||||||
|  | 	comments []*ast.CommentGroup | ||||||
|  | 
 | ||||||
|  | 	fset   *token.FileSet | ||||||
|  | 	issues []Issue | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *Linter) Run(fset *token.FileSet, nodes ...ast.Node) ([]Issue, error) { | ||||||
|  | 	var issues []Issue //nolint:prealloc // we don't know how many there will be
 | ||||||
|  | 	for _, node := range nodes { | ||||||
|  | 		var comments []*ast.CommentGroup | ||||||
|  | 		isTestFile := false | ||||||
|  | 		isWholeFileExample := false | ||||||
|  | 		if file, ok := node.(*ast.File); ok { | ||||||
|  | 			comments = file.Comments | ||||||
|  | 			fileName := fset.Position(file.Pos()).Filename | ||||||
|  | 			isTestFile = strings.HasSuffix(fileName, "_test.go") | ||||||
|  | 
 | ||||||
|  | 			// From https://blog.golang.org/examples, a "whole file example" is:
 | ||||||
|  | 			// a file that ends in _test.go and contains exactly one example function,
 | ||||||
|  | 			// no test or benchmark functions, and at least one other package-level declaration.
 | ||||||
|  | 			if l.cfg.ExcludeGodocExamples && isTestFile && len(file.Decls) > 1 { | ||||||
|  | 				numExamples := 0 | ||||||
|  | 				numTestsAndBenchmarks := 0 | ||||||
|  | 				for _, decl := range file.Decls { | ||||||
|  | 					funcDecl, isFuncDecl := decl.(*ast.FuncDecl) | ||||||
|  | 					// consider only functions, not methods
 | ||||||
|  | 					if !isFuncDecl || funcDecl.Recv != nil || funcDecl.Name == nil { | ||||||
|  | 						continue | ||||||
|  | 					} | ||||||
|  | 					funcName := funcDecl.Name.Name | ||||||
|  | 					if strings.HasPrefix(funcName, "Test") || strings.HasPrefix(funcName, "Benchmark") { | ||||||
|  | 						numTestsAndBenchmarks++ | ||||||
|  | 						break // not a whole file example
 | ||||||
|  | 					} | ||||||
|  | 					if strings.HasPrefix(funcName, "Example") { | ||||||
|  | 						numExamples++ | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				// if this is a whole file example, skip this node
 | ||||||
|  | 				isWholeFileExample = numExamples == 1 && numTestsAndBenchmarks == 0 | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if isWholeFileExample { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		visitor := visitor{ | ||||||
|  | 			cfg:        l.cfg, | ||||||
|  | 			isTestFile: isTestFile, | ||||||
|  | 			linter:     l, | ||||||
|  | 			fset:       fset, | ||||||
|  | 			comments:   comments, | ||||||
|  | 		} | ||||||
|  | 		ast.Walk(&visitor, node) | ||||||
|  | 		issues = append(issues, visitor.issues...) | ||||||
|  | 	} | ||||||
|  | 	return issues, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *visitor) Visit(node ast.Node) ast.Visitor { | ||||||
|  | 	switch node := node.(type) { | ||||||
|  | 	case *ast.FuncDecl: | ||||||
|  | 		// don't descend into godoc examples if we are ignoring them
 | ||||||
|  | 		isGodocExample := v.isTestFile && node.Recv == nil && node.Name != nil && strings.HasPrefix(node.Name.Name, "Example") | ||||||
|  | 		if isGodocExample && v.cfg.ExcludeGodocExamples { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		return v | ||||||
|  | 	case *ast.SelectorExpr: | ||||||
|  | 	case *ast.Ident: | ||||||
|  | 	default: | ||||||
|  | 		return v | ||||||
|  | 	} | ||||||
|  | 	for _, p := range v.linter.patterns { | ||||||
|  | 		if p.MatchString(v.textFor(node)) && !v.permit(node) { | ||||||
|  | 			v.issues = append(v.issues, UsedIssue{ | ||||||
|  | 				identifier: v.textFor(node), | ||||||
|  | 				pattern:    p.String(), | ||||||
|  | 				position:   v.fset.Position(node.Pos()), | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *visitor) textFor(node ast.Node) string { | ||||||
|  | 	buf := new(bytes.Buffer) | ||||||
|  | 	if err := printer.Fprint(buf, v.fset, node); err != nil { | ||||||
|  | 		log.Fatalf("ERROR: unable to print node at %s: %s", v.fset.Position(node.Pos()), err) | ||||||
|  | 	} | ||||||
|  | 	return buf.String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *visitor) permit(node ast.Node) bool { | ||||||
|  | 	if v.cfg.IgnorePermitDirectives { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	nodePos := v.fset.Position(node.Pos()) | ||||||
|  | 	var nolint = regexp.MustCompile(fmt.Sprintf(`^//\s?permit:%s\b`, regexp.QuoteMeta(v.textFor(node)))) | ||||||
|  | 	for _, c := range v.comments { | ||||||
|  | 		commentPos := v.fset.Position(c.Pos()) | ||||||
|  | 		if commentPos.Line == nodePos.Line && len(c.List) > 0 && nolint.MatchString(c.List[0].Text) { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | Copyright 2019 Andrew Shannon Brown | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
							
								
								
									
										200
									
								
								tests/tools/vendor/github.com/ashanbrown/makezero/makezero/makezero.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										200
									
								
								tests/tools/vendor/github.com/ashanbrown/makezero/makezero/makezero.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,200 @@ | ||||||
|  | // makezero provides a linter for appends to slices initialized with non-zero length.
 | ||||||
|  | package makezero | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/printer" | ||||||
|  | 	"go/token" | ||||||
|  | 	"go/types" | ||||||
|  | 	"log" | ||||||
|  | 	"regexp" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type Issue interface { | ||||||
|  | 	Details() string | ||||||
|  | 	Position() token.Position | ||||||
|  | 	String() string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type AppendIssue struct { | ||||||
|  | 	name     string | ||||||
|  | 	position token.Position | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a AppendIssue) Details() string { | ||||||
|  | 	return fmt.Sprintf("append to slice `%s` with non-zero initialized length", a.name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a AppendIssue) Position() token.Position { | ||||||
|  | 	return a.position | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a AppendIssue) String() string { return toString(a) } | ||||||
|  | 
 | ||||||
|  | type MustHaveNonZeroInitLenIssue struct { | ||||||
|  | 	name     string | ||||||
|  | 	position token.Position | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (i MustHaveNonZeroInitLenIssue) Details() string { | ||||||
|  | 	return fmt.Sprintf("slice `%s` does not have non-zero initial length", i.name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (i MustHaveNonZeroInitLenIssue) Position() token.Position { | ||||||
|  | 	return i.position | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (i MustHaveNonZeroInitLenIssue) String() string { return toString(i) } | ||||||
|  | 
 | ||||||
|  | func toString(i Issue) string { | ||||||
|  | 	return fmt.Sprintf("%s at %s", i.Details(), i.Position()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type visitor struct { | ||||||
|  | 	initLenMustBeZero bool | ||||||
|  | 
 | ||||||
|  | 	comments []*ast.CommentGroup // comments to apply during this visit
 | ||||||
|  | 	info     *types.Info | ||||||
|  | 
 | ||||||
|  | 	nonZeroLengthSliceDecls map[interface{}]struct{} | ||||||
|  | 	fset                    *token.FileSet | ||||||
|  | 	issues                  []Issue | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Linter struct { | ||||||
|  | 	initLenMustBeZero bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewLinter(initialLengthMustBeZero bool) *Linter { | ||||||
|  | 	return &Linter{ | ||||||
|  | 		initLenMustBeZero: initialLengthMustBeZero, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l Linter) Run(fset *token.FileSet, info *types.Info, nodes ...ast.Node) ([]Issue, error) { | ||||||
|  | 	var issues []Issue // nolint:prealloc // don't know how many there will be
 | ||||||
|  | 	for _, node := range nodes { | ||||||
|  | 		var comments []*ast.CommentGroup | ||||||
|  | 		if file, ok := node.(*ast.File); ok { | ||||||
|  | 			comments = file.Comments | ||||||
|  | 		} | ||||||
|  | 		visitor := visitor{ | ||||||
|  | 			nonZeroLengthSliceDecls: make(map[interface{}]struct{}), | ||||||
|  | 			initLenMustBeZero:       l.initLenMustBeZero, | ||||||
|  | 			info:                    info, | ||||||
|  | 			fset:                    fset, | ||||||
|  | 			comments:                comments, | ||||||
|  | 		} | ||||||
|  | 		ast.Walk(&visitor, node) | ||||||
|  | 		issues = append(issues, visitor.issues...) | ||||||
|  | 	} | ||||||
|  | 	return issues, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *visitor) Visit(node ast.Node) ast.Visitor { | ||||||
|  | 	switch node := node.(type) { | ||||||
|  | 	case *ast.CallExpr: | ||||||
|  | 		fun, ok := node.Fun.(*ast.Ident) | ||||||
|  | 		if !ok || fun.Name != "append" { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		if sliceIdent, ok := node.Args[0].(*ast.Ident); ok && | ||||||
|  | 			v.hasNonZeroInitialLength(sliceIdent) && | ||||||
|  | 			!v.hasNoLintOnSameLine(fun) { | ||||||
|  | 			v.issues = append(v.issues, AppendIssue{name: sliceIdent.Name, position: v.fset.Position(fun.Pos())}) | ||||||
|  | 		} | ||||||
|  | 	case *ast.AssignStmt: | ||||||
|  | 		for i, right := range node.Rhs { | ||||||
|  | 			if right, ok := right.(*ast.CallExpr); ok { | ||||||
|  | 				fun, ok := right.Fun.(*ast.Ident) | ||||||
|  | 				if !ok || fun.Name != "make" { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 				left := node.Lhs[i] | ||||||
|  | 				if len(right.Args) == 2 { | ||||||
|  | 					// ignore if not a slice or it has explicit zero length
 | ||||||
|  | 					if !v.isSlice(right.Args[0]) { | ||||||
|  | 						break | ||||||
|  | 					} else if lit, ok := right.Args[1].(*ast.BasicLit); ok && lit.Kind == token.INT && lit.Value == "0" { | ||||||
|  | 						break | ||||||
|  | 					} | ||||||
|  | 					if v.initLenMustBeZero && !v.hasNoLintOnSameLine(fun) { | ||||||
|  | 						v.issues = append(v.issues, MustHaveNonZeroInitLenIssue{ | ||||||
|  | 							name:     v.textFor(left), | ||||||
|  | 							position: v.fset.Position(node.Pos()), | ||||||
|  | 						}) | ||||||
|  | 					} | ||||||
|  | 					v.recordNonZeroLengthSlices(left) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return v | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *visitor) textFor(node ast.Node) string { | ||||||
|  | 	typeBuf := new(bytes.Buffer) | ||||||
|  | 	if err := printer.Fprint(typeBuf, v.fset, node); err != nil { | ||||||
|  | 		log.Fatalf("ERROR: unable to print type: %s", err) | ||||||
|  | 	} | ||||||
|  | 	return typeBuf.String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *visitor) hasNonZeroInitialLength(ident *ast.Ident) bool { | ||||||
|  | 	if ident.Obj == nil { | ||||||
|  | 		log.Printf("WARNING: could not determine with %q at %s is a slice (missing object type)", | ||||||
|  | 			ident.Name, v.fset.Position(ident.Pos()).String()) | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	_, exists := v.nonZeroLengthSliceDecls[ident.Obj.Decl] | ||||||
|  | 	return exists | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *visitor) recordNonZeroLengthSlices(node ast.Node) { | ||||||
|  | 	ident, ok := node.(*ast.Ident) | ||||||
|  | 	if !ok { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if ident.Obj == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	v.nonZeroLengthSliceDecls[ident.Obj.Decl] = struct{}{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *visitor) isSlice(node ast.Node) bool { | ||||||
|  | 	// determine type if this is a user-defined type
 | ||||||
|  | 	if ident, ok := node.(*ast.Ident); ok { | ||||||
|  | 		obj := ident.Obj | ||||||
|  | 		if obj == nil { | ||||||
|  | 			if v.info != nil { | ||||||
|  | 				_, ok := v.info.ObjectOf(ident).Type().(*types.Slice) | ||||||
|  | 				return ok | ||||||
|  | 			} | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 		spec, ok := obj.Decl.(*ast.TypeSpec) | ||||||
|  | 		if !ok { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 		node = spec.Type | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if node, ok := node.(*ast.ArrayType); ok { | ||||||
|  | 		return node.Len == nil // only slices have zero length
 | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *visitor) hasNoLintOnSameLine(node ast.Node) bool { | ||||||
|  | 	var nolint = regexp.MustCompile(`^\s*nozero\b`) | ||||||
|  | 	nodePos := v.fset.Position(node.Pos()) | ||||||
|  | 	for _, c := range v.comments { | ||||||
|  | 		commentPos := v.fset.Position(c.Pos()) | ||||||
|  | 		if commentPos.Line == nodePos.Line && nolint.MatchString(c.Text()) { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | Copyright (C) 2013 Blake Mizerany | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
							
								
								
									
										2388
									
								
								tests/tools/vendor/github.com/beorn7/perks/quantile/exampledata.txt
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										2388
									
								
								tests/tools/vendor/github.com/beorn7/perks/quantile/exampledata.txt
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,316 @@ | ||||||
|  | // Package quantile computes approximate quantiles over an unbounded data
 | ||||||
|  | // stream within low memory and CPU bounds.
 | ||||||
|  | //
 | ||||||
|  | // A small amount of accuracy is traded to achieve the above properties.
 | ||||||
|  | //
 | ||||||
|  | // Multiple streams can be merged before calling Query to generate a single set
 | ||||||
|  | // of results. This is meaningful when the streams represent the same type of
 | ||||||
|  | // data. See Merge and Samples.
 | ||||||
|  | //
 | ||||||
|  | // For more detailed information about the algorithm used, see:
 | ||||||
|  | //
 | ||||||
|  | // Effective Computation of Biased Quantiles over Data Streams
 | ||||||
|  | //
 | ||||||
|  | // http://www.cs.rutgers.edu/~muthu/bquant.pdf
 | ||||||
|  | package quantile | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"math" | ||||||
|  | 	"sort" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Sample holds an observed value and meta information for compression. JSON
 | ||||||
|  | // tags have been added for convenience.
 | ||||||
|  | type Sample struct { | ||||||
|  | 	Value float64 `json:",string"` | ||||||
|  | 	Width float64 `json:",string"` | ||||||
|  | 	Delta float64 `json:",string"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Samples represents a slice of samples. It implements sort.Interface.
 | ||||||
|  | type Samples []Sample | ||||||
|  | 
 | ||||||
|  | func (a Samples) Len() int           { return len(a) } | ||||||
|  | func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value } | ||||||
|  | func (a Samples) Swap(i, j int)      { a[i], a[j] = a[j], a[i] } | ||||||
|  | 
 | ||||||
|  | type invariant func(s *stream, r float64) float64 | ||||||
|  | 
 | ||||||
|  | // NewLowBiased returns an initialized Stream for low-biased quantiles
 | ||||||
|  | // (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
 | ||||||
|  | // error guarantees can still be given even for the lower ranks of the data
 | ||||||
|  | // distribution.
 | ||||||
|  | //
 | ||||||
|  | // The provided epsilon is a relative error, i.e. the true quantile of a value
 | ||||||
|  | // returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
 | ||||||
|  | //
 | ||||||
|  | // See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
 | ||||||
|  | // properties.
 | ||||||
|  | func NewLowBiased(epsilon float64) *Stream { | ||||||
|  | 	ƒ := func(s *stream, r float64) float64 { | ||||||
|  | 		return 2 * epsilon * r | ||||||
|  | 	} | ||||||
|  | 	return newStream(ƒ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewHighBiased returns an initialized Stream for high-biased quantiles
 | ||||||
|  | // (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
 | ||||||
|  | // error guarantees can still be given even for the higher ranks of the data
 | ||||||
|  | // distribution.
 | ||||||
|  | //
 | ||||||
|  | // The provided epsilon is a relative error, i.e. the true quantile of a value
 | ||||||
|  | // returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
 | ||||||
|  | //
 | ||||||
|  | // See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
 | ||||||
|  | // properties.
 | ||||||
|  | func NewHighBiased(epsilon float64) *Stream { | ||||||
|  | 	ƒ := func(s *stream, r float64) float64 { | ||||||
|  | 		return 2 * epsilon * (s.n - r) | ||||||
|  | 	} | ||||||
|  | 	return newStream(ƒ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewTargeted returns an initialized Stream concerned with a particular set of
 | ||||||
|  | // quantile values that are supplied a priori. Knowing these a priori reduces
 | ||||||
|  | // space and computation time. The targets map maps the desired quantiles to
 | ||||||
|  | // their absolute errors, i.e. the true quantile of a value returned by a query
 | ||||||
|  | // is guaranteed to be within (Quantile±Epsilon).
 | ||||||
|  | //
 | ||||||
|  | // See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
 | ||||||
|  | func NewTargeted(targetMap map[float64]float64) *Stream { | ||||||
|  | 	// Convert map to slice to avoid slow iterations on a map.
 | ||||||
|  | 	// ƒ is called on the hot path, so converting the map to a slice
 | ||||||
|  | 	// beforehand results in significant CPU savings.
 | ||||||
|  | 	targets := targetMapToSlice(targetMap) | ||||||
|  | 
 | ||||||
|  | 	ƒ := func(s *stream, r float64) float64 { | ||||||
|  | 		var m = math.MaxFloat64 | ||||||
|  | 		var f float64 | ||||||
|  | 		for _, t := range targets { | ||||||
|  | 			if t.quantile*s.n <= r { | ||||||
|  | 				f = (2 * t.epsilon * r) / t.quantile | ||||||
|  | 			} else { | ||||||
|  | 				f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile) | ||||||
|  | 			} | ||||||
|  | 			if f < m { | ||||||
|  | 				m = f | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return m | ||||||
|  | 	} | ||||||
|  | 	return newStream(ƒ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type target struct { | ||||||
|  | 	quantile float64 | ||||||
|  | 	epsilon  float64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func targetMapToSlice(targetMap map[float64]float64) []target { | ||||||
|  | 	targets := make([]target, 0, len(targetMap)) | ||||||
|  | 
 | ||||||
|  | 	for quantile, epsilon := range targetMap { | ||||||
|  | 		t := target{ | ||||||
|  | 			quantile: quantile, | ||||||
|  | 			epsilon:  epsilon, | ||||||
|  | 		} | ||||||
|  | 		targets = append(targets, t) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return targets | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Stream computes quantiles for a stream of float64s. It is not thread-safe by
 | ||||||
|  | // design. Take care when using across multiple goroutines.
 | ||||||
|  | type Stream struct { | ||||||
|  | 	*stream | ||||||
|  | 	b      Samples | ||||||
|  | 	sorted bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newStream(ƒ invariant) *Stream { | ||||||
|  | 	x := &stream{ƒ: ƒ} | ||||||
|  | 	return &Stream{x, make(Samples, 0, 500), true} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Insert inserts v into the stream.
 | ||||||
|  | func (s *Stream) Insert(v float64) { | ||||||
|  | 	s.insert(Sample{Value: v, Width: 1}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Stream) insert(sample Sample) { | ||||||
|  | 	s.b = append(s.b, sample) | ||||||
|  | 	s.sorted = false | ||||||
|  | 	if len(s.b) == cap(s.b) { | ||||||
|  | 		s.flush() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Query returns the computed qth percentiles value. If s was created with
 | ||||||
|  | // NewTargeted, and q is not in the set of quantiles provided a priori, Query
 | ||||||
|  | // will return an unspecified result.
 | ||||||
|  | func (s *Stream) Query(q float64) float64 { | ||||||
|  | 	if !s.flushed() { | ||||||
|  | 		// Fast path when there hasn't been enough data for a flush;
 | ||||||
|  | 		// this also yields better accuracy for small sets of data.
 | ||||||
|  | 		l := len(s.b) | ||||||
|  | 		if l == 0 { | ||||||
|  | 			return 0 | ||||||
|  | 		} | ||||||
|  | 		i := int(math.Ceil(float64(l) * q)) | ||||||
|  | 		if i > 0 { | ||||||
|  | 			i -= 1 | ||||||
|  | 		} | ||||||
|  | 		s.maybeSort() | ||||||
|  | 		return s.b[i].Value | ||||||
|  | 	} | ||||||
|  | 	s.flush() | ||||||
|  | 	return s.stream.query(q) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Merge merges samples into the underlying streams samples. This is handy when
 | ||||||
|  | // merging multiple streams from separate threads, database shards, etc.
 | ||||||
|  | //
 | ||||||
|  | // ATTENTION: This method is broken and does not yield correct results. The
 | ||||||
|  | // underlying algorithm is not capable of merging streams correctly.
 | ||||||
|  | func (s *Stream) Merge(samples Samples) { | ||||||
|  | 	sort.Sort(samples) | ||||||
|  | 	s.stream.merge(samples) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Reset reinitializes and clears the list reusing the samples buffer memory.
 | ||||||
|  | func (s *Stream) Reset() { | ||||||
|  | 	s.stream.reset() | ||||||
|  | 	s.b = s.b[:0] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Samples returns stream samples held by s.
 | ||||||
|  | func (s *Stream) Samples() Samples { | ||||||
|  | 	if !s.flushed() { | ||||||
|  | 		return s.b | ||||||
|  | 	} | ||||||
|  | 	s.flush() | ||||||
|  | 	return s.stream.samples() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Count returns the total number of samples observed in the stream
 | ||||||
|  | // since initialization.
 | ||||||
|  | func (s *Stream) Count() int { | ||||||
|  | 	return len(s.b) + s.stream.count() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Stream) flush() { | ||||||
|  | 	s.maybeSort() | ||||||
|  | 	s.stream.merge(s.b) | ||||||
|  | 	s.b = s.b[:0] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Stream) maybeSort() { | ||||||
|  | 	if !s.sorted { | ||||||
|  | 		s.sorted = true | ||||||
|  | 		sort.Sort(s.b) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Stream) flushed() bool { | ||||||
|  | 	return len(s.stream.l) > 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type stream struct { | ||||||
|  | 	n float64 | ||||||
|  | 	l []Sample | ||||||
|  | 	ƒ invariant | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *stream) reset() { | ||||||
|  | 	s.l = s.l[:0] | ||||||
|  | 	s.n = 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *stream) insert(v float64) { | ||||||
|  | 	s.merge(Samples{{v, 1, 0}}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *stream) merge(samples Samples) { | ||||||
|  | 	// TODO(beorn7): This tries to merge not only individual samples, but
 | ||||||
|  | 	// whole summaries. The paper doesn't mention merging summaries at
 | ||||||
|  | 	// all. Unittests show that the merging is inaccurate. Find out how to
 | ||||||
|  | 	// do merges properly.
 | ||||||
|  | 	var r float64 | ||||||
|  | 	i := 0 | ||||||
|  | 	for _, sample := range samples { | ||||||
|  | 		for ; i < len(s.l); i++ { | ||||||
|  | 			c := s.l[i] | ||||||
|  | 			if c.Value > sample.Value { | ||||||
|  | 				// Insert at position i.
 | ||||||
|  | 				s.l = append(s.l, Sample{}) | ||||||
|  | 				copy(s.l[i+1:], s.l[i:]) | ||||||
|  | 				s.l[i] = Sample{ | ||||||
|  | 					sample.Value, | ||||||
|  | 					sample.Width, | ||||||
|  | 					math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1), | ||||||
|  | 					// TODO(beorn7): How to calculate delta correctly?
 | ||||||
|  | 				} | ||||||
|  | 				i++ | ||||||
|  | 				goto inserted | ||||||
|  | 			} | ||||||
|  | 			r += c.Width | ||||||
|  | 		} | ||||||
|  | 		s.l = append(s.l, Sample{sample.Value, sample.Width, 0}) | ||||||
|  | 		i++ | ||||||
|  | 	inserted: | ||||||
|  | 		s.n += sample.Width | ||||||
|  | 		r += sample.Width | ||||||
|  | 	} | ||||||
|  | 	s.compress() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *stream) count() int { | ||||||
|  | 	return int(s.n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *stream) query(q float64) float64 { | ||||||
|  | 	t := math.Ceil(q * s.n) | ||||||
|  | 	t += math.Ceil(s.ƒ(s, t) / 2) | ||||||
|  | 	p := s.l[0] | ||||||
|  | 	var r float64 | ||||||
|  | 	for _, c := range s.l[1:] { | ||||||
|  | 		r += p.Width | ||||||
|  | 		if r+c.Width+c.Delta > t { | ||||||
|  | 			return p.Value | ||||||
|  | 		} | ||||||
|  | 		p = c | ||||||
|  | 	} | ||||||
|  | 	return p.Value | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *stream) compress() { | ||||||
|  | 	if len(s.l) < 2 { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	x := s.l[len(s.l)-1] | ||||||
|  | 	xi := len(s.l) - 1 | ||||||
|  | 	r := s.n - 1 - x.Width | ||||||
|  | 
 | ||||||
|  | 	for i := len(s.l) - 2; i >= 0; i-- { | ||||||
|  | 		c := s.l[i] | ||||||
|  | 		if c.Width+x.Width+x.Delta <= s.ƒ(s, r) { | ||||||
|  | 			x.Width += c.Width | ||||||
|  | 			s.l[xi] = x | ||||||
|  | 			// Remove element at i.
 | ||||||
|  | 			copy(s.l[i:], s.l[i+1:]) | ||||||
|  | 			s.l = s.l[:len(s.l)-1] | ||||||
|  | 			xi -= 1 | ||||||
|  | 		} else { | ||||||
|  | 			x = c | ||||||
|  | 			xi = i | ||||||
|  | 		} | ||||||
|  | 		r -= c.Width | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *stream) samples() Samples { | ||||||
|  | 	samples := make(Samples, len(s.l)) | ||||||
|  | 	copy(samples, s.l) | ||||||
|  | 	return samples | ||||||
|  | } | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | MIT License | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2020 Bartłomiej Klimczak | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
							
								
								
									
										104
									
								
								tests/tools/vendor/github.com/bkielbasa/cyclop/pkg/analyzer/analyzer.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										104
									
								
								tests/tools/vendor/github.com/bkielbasa/cyclop/pkg/analyzer/analyzer.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,104 @@ | ||||||
|  | package analyzer | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"flag" | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/token" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/go/analysis" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	flagSet flag.FlagSet | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var maxComplexity int | ||||||
|  | var packageAverage float64 | ||||||
|  | var skipTests bool | ||||||
|  | 
 | ||||||
|  | func NewAnalyzer() *analysis.Analyzer { | ||||||
|  | 	return &analysis.Analyzer{ | ||||||
|  | 		Name:  "cyclop", | ||||||
|  | 		Doc:   "calculates cyclomatic complexity", | ||||||
|  | 		Run:   run, | ||||||
|  | 		Flags: flagSet, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	flagSet.IntVar(&maxComplexity, "maxComplexity", 10, "max complexity the function can have") | ||||||
|  | 	flagSet.Float64Var(&packageAverage, "packageAverage", 0, "max avarage complexity in package") | ||||||
|  | 	flagSet.BoolVar(&skipTests, "skipTests", false, "should the linter execute on test files as well") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func run(pass *analysis.Pass) (interface{}, error) { | ||||||
|  | 	var sum, count float64 | ||||||
|  | 	var pkgName string | ||||||
|  | 	var pkgPos token.Pos | ||||||
|  | 
 | ||||||
|  | 	for _, f := range pass.Files { | ||||||
|  | 		ast.Inspect(f, func(node ast.Node) bool { | ||||||
|  | 			f, ok := node.(*ast.FuncDecl) | ||||||
|  | 			if !ok { | ||||||
|  | 				if node == nil { | ||||||
|  | 					return true | ||||||
|  | 				} | ||||||
|  | 				if file, ok := node.(*ast.File); ok { | ||||||
|  | 					pkgName = file.Name.Name | ||||||
|  | 					pkgPos = node.Pos() | ||||||
|  | 				} | ||||||
|  | 				// we check function by function
 | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if skipTests && testFunc(f) { | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			count++ | ||||||
|  | 			comp := complexity(f) | ||||||
|  | 			sum += float64(comp) | ||||||
|  | 			if comp > maxComplexity { | ||||||
|  | 				pass.Reportf(node.Pos(), "calculated cyclomatic complexity for function %s is %d, max is %d", f.Name.Name, comp, maxComplexity) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return true | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if packageAverage > 0 { | ||||||
|  | 		avg := sum / count | ||||||
|  | 		if avg > packageAverage { | ||||||
|  | 			pass.Reportf(pkgPos, "the avarage complexity for the package %s is %f, max is %f", pkgName, avg, packageAverage) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func testFunc(f *ast.FuncDecl) bool { | ||||||
|  | 	return strings.HasPrefix(f.Name.Name, "Test") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func complexity(fn *ast.FuncDecl) int { | ||||||
|  | 	v := complexityVisitor{} | ||||||
|  | 	ast.Walk(&v, fn) | ||||||
|  | 	return v.Complexity | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type complexityVisitor struct { | ||||||
|  | 	Complexity int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v *complexityVisitor) Visit(n ast.Node) ast.Visitor { | ||||||
|  | 	switch n := n.(type) { | ||||||
|  | 	case *ast.FuncDecl, *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause: | ||||||
|  | 		v.Complexity++ | ||||||
|  | 	case *ast.BinaryExpr: | ||||||
|  | 		if n.Op == token.LAND || n.Op == token.LOR { | ||||||
|  | 			v.Complexity++ | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return v | ||||||
|  | } | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | root = true | ||||||
|  | 
 | ||||||
|  | [**] | ||||||
|  | charset = utf-8 | ||||||
|  | end_of_line = lf | ||||||
|  | insert_final_newline = true | ||||||
|  | indent_style = tab | ||||||
|  | indent_size = 4 | ||||||
|  | trim_trailing_whitespace = true | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | /cmd/__debug_bin | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | linters: | ||||||
|  |   enable: | ||||||
|  |     - gocognit | ||||||
|  |     - gocritic | ||||||
|  |     - gocyclo | ||||||
|  |     - goerr113 | ||||||
|  |     - golint | ||||||
|  |     - interfacer | ||||||
|  |     - nakedret | ||||||
|  |     - prealloc | ||||||
|  |     - unconvert | ||||||
|  |     - unparam | ||||||
|  | 
 | ||||||
|  | linters-settings: | ||||||
|  |   gocognit: | ||||||
|  |     min-complexity: 15 | ||||||
|  |   gocyclo: | ||||||
|  |     min-complexity: 10 | ||||||
|  |   nakedret: | ||||||
|  |     max-func-lines: 0 | ||||||
|  | @ -0,0 +1,7 @@ | ||||||
|  | language: go | ||||||
|  | go: | ||||||
|  |   - "1.15" | ||||||
|  | before_script: | ||||||
|  |   - go get github.com/mattn/goveralls | ||||||
|  | after_script: | ||||||
|  |   - goveralls -service=travis-ci | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | Copyright 2021 Maik Schreiber | ||||||
|  | 
 | ||||||
|  | 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,89 @@ | ||||||
|  | [](https://app.travis-ci.com/github/blizzy78/varnamelen) [](https://coveralls.io/github/blizzy78/varnamelen?branch=master) [](https://pkg.go.dev/github.com/blizzy78/varnamelen) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | varnamelen | ||||||
|  | ========== | ||||||
|  | 
 | ||||||
|  | A Go Analyzer checking that the length of a variable's name matches its usage scope. | ||||||
|  | 
 | ||||||
|  | A variable with a short name can be hard to use if the variable is used over a longer span of lines of code. | ||||||
|  | A longer variable name may be easier to comprehend. | ||||||
|  | 
 | ||||||
|  | The analyzer can check variable names, method receiver names, as well as named return values. | ||||||
|  | 
 | ||||||
|  | Conventional Go parameters such as `ctx context.Context` or `t *testing.T` will always be ignored. | ||||||
|  | 
 | ||||||
|  | **Example output** | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | test.go:4:2: variable name 'x' is too short for the scope of its usage (varnamelen) | ||||||
|  |         x := 123 | ||||||
|  |         ^ | ||||||
|  | test.go:6:2: variable name 'i' is too short for the scope of its usage (varnamelen) | ||||||
|  |         i := 10 | ||||||
|  |         ^ | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Standalone Usage | ||||||
|  | ---------------- | ||||||
|  | 
 | ||||||
|  | The `cmd/` folder provides a standalone command line utility. You can build it like this: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | go build -o varnamelen ./cmd/ | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | **Usage** | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | varnamelen: checks that the length of a variable's name matches its scope | ||||||
|  | 
 | ||||||
|  | Usage: varnamelen [-flag] [package] | ||||||
|  | 
 | ||||||
|  | A variable with a short name can be hard to use if the variable is used | ||||||
|  | over a longer span of lines of code. A longer variable name may be easier | ||||||
|  | to comprehend. | ||||||
|  | 
 | ||||||
|  | Flags: | ||||||
|  |   -V	print version and exit | ||||||
|  |   -all | ||||||
|  |     	no effect (deprecated) | ||||||
|  |   -c int | ||||||
|  |     	display offending line with this many lines of context (default -1) | ||||||
|  |   -checkReceiver | ||||||
|  |     	check method receiver names | ||||||
|  |   -checkReturn | ||||||
|  |     	check named return values | ||||||
|  |   -cpuprofile string | ||||||
|  |     	write CPU profile to this file | ||||||
|  |   -debug string | ||||||
|  |     	debug flags, any subset of "fpstv" | ||||||
|  |   -fix | ||||||
|  |     	apply all suggested fixes | ||||||
|  |   -flags | ||||||
|  |     	print analyzer flags in JSON | ||||||
|  |   -ignoreNames value | ||||||
|  |     	comma-separated list of ignored variable names | ||||||
|  |   -json | ||||||
|  |     	emit JSON output | ||||||
|  |   -maxDistance int | ||||||
|  |     	maximum number of lines of variable usage scope considered 'short' (default 5) | ||||||
|  |   -memprofile string | ||||||
|  |     	write memory profile to this file | ||||||
|  |   -minNameLength int | ||||||
|  |     	minimum length of variable name considered 'long' (default 3) | ||||||
|  |   -source | ||||||
|  |     	no effect (deprecated) | ||||||
|  |   -tags string | ||||||
|  |     	no effect (deprecated) | ||||||
|  |   -trace string | ||||||
|  |     	write trace log to this file | ||||||
|  |   -v	no effect (deprecated) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | License | ||||||
|  | ------- | ||||||
|  | 
 | ||||||
|  | This package is licensed under the MIT license. | ||||||
|  | @ -0,0 +1,10 @@ | ||||||
|  | module github.com/blizzy78/varnamelen | ||||||
|  | 
 | ||||||
|  | go 1.15 | ||||||
|  | 
 | ||||||
|  | require ( | ||||||
|  | 	github.com/matryer/is v1.4.0 | ||||||
|  | 	golang.org/x/mod v0.5.0 // indirect | ||||||
|  | 	golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 // indirect | ||||||
|  | 	golang.org/x/tools v0.1.6 | ||||||
|  | ) | ||||||
|  | @ -0,0 +1,31 @@ | ||||||
|  | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= | ||||||
|  | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= | ||||||
|  | github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= | ||||||
|  | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||||
|  | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||||
|  | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||||
|  | golang.org/x/mod v0.5.0 h1:UG21uOlmZabA4fW5i7ZX6bjw1xELEGg/ZLgZq9auk/Q= | ||||||
|  | golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= | ||||||
|  | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||||
|  | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||||
|  | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||||
|  | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|  | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
|  | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 h1:J27LZFQBFoihqXoegpscI10HpjZ7B5WQLLKL2FZXQKw= | ||||||
|  | golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||||
|  | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||||
|  | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
|  | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
|  | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||||
|  | golang.org/x/tools v0.1.6 h1:SIasE1FVIQOWz2GEAHFOmoW7xchJcqlucjSULTL0Ag4= | ||||||
|  | golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= | ||||||
|  | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= | ||||||
|  | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
							
								
								
									
										13
									
								
								tests/tools/vendor/github.com/blizzy78/varnamelen/varnamelen.code-workspace
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										13
									
								
								tests/tools/vendor/github.com/blizzy78/varnamelen/varnamelen.code-workspace
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | { | ||||||
|  | 	"folders": [ | ||||||
|  | 		{ | ||||||
|  | 			"path": "." | ||||||
|  | 		} | ||||||
|  | 	], | ||||||
|  | 	"extensions": { | ||||||
|  | 		"recommendations": [ | ||||||
|  | 			"EditorConfig.EditorConfig", | ||||||
|  | 			"golang.go" | ||||||
|  | 		] | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,342 @@ | ||||||
|  | package varnamelen | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"go/ast" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/go/analysis" | ||||||
|  | 	"golang.org/x/tools/go/analysis/passes/inspect" | ||||||
|  | 	"golang.org/x/tools/go/ast/inspector" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // varNameLen is an analyzer that checks that the length of a variable's name matches its usage scope.
 | ||||||
|  | // It will create a report for a variable's assignment if that variable has a short name, but its
 | ||||||
|  | // usage scope is not considered "small."
 | ||||||
|  | type varNameLen struct { | ||||||
|  | 	// maxDistance is the longest distance, in source lines, that is being considered a "small scope."
 | ||||||
|  | 	maxDistance int | ||||||
|  | 
 | ||||||
|  | 	// minNameLength is the minimum length of a variable's name that is considered "long."
 | ||||||
|  | 	minNameLength int | ||||||
|  | 
 | ||||||
|  | 	// ignoreNames is an optional list of variable names that should be ignored completely.
 | ||||||
|  | 	ignoreNames stringsValue | ||||||
|  | 
 | ||||||
|  | 	// checkReceiver determines whether a method receiver's name should be checked.
 | ||||||
|  | 	checkReceiver bool | ||||||
|  | 
 | ||||||
|  | 	// checkReturn determines whether named return values should be checked.
 | ||||||
|  | 	checkReturn bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // stringsValue is the value of a list-of-strings flag.
 | ||||||
|  | type stringsValue struct { | ||||||
|  | 	Values []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // variable represents a declared variable.
 | ||||||
|  | type variable struct { | ||||||
|  | 	// name is the name of the variable.
 | ||||||
|  | 	name string | ||||||
|  | 
 | ||||||
|  | 	// assign is the assign statement that declares the variable.
 | ||||||
|  | 	assign *ast.AssignStmt | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // parameter represents a declared function or method parameter.
 | ||||||
|  | type parameter struct { | ||||||
|  | 	// name is the name of the parameter.
 | ||||||
|  | 	name string | ||||||
|  | 
 | ||||||
|  | 	// field is the declaration of the parameter.
 | ||||||
|  | 	field *ast.Field | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// defaultMaxDistance is the default value for the maximum distance between the declaration of a variable and its usage
 | ||||||
|  | 	// that is considered a "small scope."
 | ||||||
|  | 	defaultMaxDistance = 5 | ||||||
|  | 
 | ||||||
|  | 	// defaultMinNameLength is the default value for the minimum length of a variable's name that is considered "long."
 | ||||||
|  | 	defaultMinNameLength = 3 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // NewAnalyzer returns a new analyzer that checks variable name length.
 | ||||||
|  | func NewAnalyzer() *analysis.Analyzer { | ||||||
|  | 	vnl := varNameLen{ | ||||||
|  | 		maxDistance:   defaultMaxDistance, | ||||||
|  | 		minNameLength: defaultMinNameLength, | ||||||
|  | 		ignoreNames:   stringsValue{}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	analyzer := analysis.Analyzer{ | ||||||
|  | 		Name: "varnamelen", | ||||||
|  | 		Doc: "checks that the length of a variable's name matches its scope\n\n" + | ||||||
|  | 			"A variable with a short name can be hard to use if the variable is used\n" + | ||||||
|  | 			"over a longer span of lines of code. A longer variable name may be easier\n" + | ||||||
|  | 			"to comprehend.", | ||||||
|  | 
 | ||||||
|  | 		Run: func(pass *analysis.Pass) (interface{}, error) { | ||||||
|  | 			vnl.run(pass) | ||||||
|  | 			return nil, nil | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		Requires: []*analysis.Analyzer{ | ||||||
|  | 			inspect.Analyzer, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	analyzer.Flags.IntVar(&vnl.maxDistance, "maxDistance", defaultMaxDistance, "maximum number of lines of variable usage scope considered 'short'") | ||||||
|  | 	analyzer.Flags.IntVar(&vnl.minNameLength, "minNameLength", defaultMinNameLength, "minimum length of variable name considered 'long'") | ||||||
|  | 	analyzer.Flags.Var(&vnl.ignoreNames, "ignoreNames", "comma-separated list of ignored variable names") | ||||||
|  | 	analyzer.Flags.BoolVar(&vnl.checkReceiver, "checkReceiver", false, "check method receiver names") | ||||||
|  | 	analyzer.Flags.BoolVar(&vnl.checkReturn, "checkReturn", false, "check named return values") | ||||||
|  | 
 | ||||||
|  | 	return &analyzer | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Run applies v to a package, according to pass.
 | ||||||
|  | func (v *varNameLen) run(pass *analysis.Pass) { | ||||||
|  | 	varToDist, paramToDist, returnToDist := v.distances(pass) | ||||||
|  | 
 | ||||||
|  | 	for variable, dist := range varToDist { | ||||||
|  | 		if v.checkNameAndDistance(variable.name, dist) { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		pass.Reportf(variable.assign.Pos(), "variable name '%s' is too short for the scope of its usage", variable.name) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for param, dist := range paramToDist { | ||||||
|  | 		if param.isConventional() { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if v.checkNameAndDistance(param.name, dist) { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		pass.Reportf(param.field.Pos(), "parameter name '%s' is too short for the scope of its usage", param.name) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for param, dist := range returnToDist { | ||||||
|  | 		if v.checkNameAndDistance(param.name, dist) { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		pass.Reportf(param.field.Pos(), "return value name '%s' is too short for the scope of its usage", param.name) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // checkNameAndDistance returns true when name or dist are considered "short", or when name is to be ignored.
 | ||||||
|  | func (v *varNameLen) checkNameAndDistance(name string, dist int) bool { | ||||||
|  | 	if len(name) >= v.minNameLength { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if dist <= v.maxDistance { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if v.ignoreNames.contains(name) { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // distances maps of variables or parameters and their longest usage distances.
 | ||||||
|  | func (v *varNameLen) distances(pass *analysis.Pass) (map[variable]int, map[parameter]int, map[parameter]int) { | ||||||
|  | 	assignIdents, paramIdents, returnIdents := v.idents(pass) | ||||||
|  | 
 | ||||||
|  | 	varToDist := map[variable]int{} | ||||||
|  | 
 | ||||||
|  | 	for _, ident := range assignIdents { | ||||||
|  | 		assign := ident.Obj.Decl.(*ast.AssignStmt) | ||||||
|  | 		variable := variable{ | ||||||
|  | 			name:   ident.Name, | ||||||
|  | 			assign: assign, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		useLine := pass.Fset.Position(ident.NamePos).Line | ||||||
|  | 		declLine := pass.Fset.Position(assign.Pos()).Line | ||||||
|  | 		varToDist[variable] = useLine - declLine | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	paramToDist := map[parameter]int{} | ||||||
|  | 
 | ||||||
|  | 	for _, ident := range paramIdents { | ||||||
|  | 		field := ident.Obj.Decl.(*ast.Field) | ||||||
|  | 		param := parameter{ | ||||||
|  | 			name:  ident.Name, | ||||||
|  | 			field: field, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		useLine := pass.Fset.Position(ident.NamePos).Line | ||||||
|  | 		declLine := pass.Fset.Position(field.Pos()).Line | ||||||
|  | 		paramToDist[param] = useLine - declLine | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	returnToDist := map[parameter]int{} | ||||||
|  | 
 | ||||||
|  | 	for _, ident := range returnIdents { | ||||||
|  | 		field := ident.Obj.Decl.(*ast.Field) | ||||||
|  | 		param := parameter{ | ||||||
|  | 			name:  ident.Name, | ||||||
|  | 			field: field, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		useLine := pass.Fset.Position(ident.NamePos).Line | ||||||
|  | 		declLine := pass.Fset.Position(field.Pos()).Line | ||||||
|  | 		returnToDist[param] = useLine - declLine | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return varToDist, paramToDist, returnToDist | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // idents returns Idents referencing assign statements, parameters, and return values, respectively.
 | ||||||
|  | func (v *varNameLen) idents(pass *analysis.Pass) ([]*ast.Ident, []*ast.Ident, []*ast.Ident) { //nolint:gocognit
 | ||||||
|  | 	inspector := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) | ||||||
|  | 
 | ||||||
|  | 	filter := []ast.Node{ | ||||||
|  | 		(*ast.Ident)(nil), | ||||||
|  | 		(*ast.FuncDecl)(nil), | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	funcs := []*ast.FuncDecl{} | ||||||
|  | 	methods := []*ast.FuncDecl{} | ||||||
|  | 	assignIdents := []*ast.Ident{} | ||||||
|  | 	paramIdents := []*ast.Ident{} | ||||||
|  | 	returnIdents := []*ast.Ident{} | ||||||
|  | 
 | ||||||
|  | 	inspector.Preorder(filter, func(node ast.Node) { | ||||||
|  | 		if f, ok := node.(*ast.FuncDecl); ok { | ||||||
|  | 			funcs = append(funcs, f) | ||||||
|  | 			if f.Recv != nil { | ||||||
|  | 				methods = append(methods, f) | ||||||
|  | 			} | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		ident := node.(*ast.Ident) | ||||||
|  | 		if ident.Obj == nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if _, ok := ident.Obj.Decl.(*ast.AssignStmt); ok { | ||||||
|  | 			assignIdents = append(assignIdents, ident) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if field, ok := ident.Obj.Decl.(*ast.Field); ok { | ||||||
|  | 			if isReceiver(field, methods) && !v.checkReceiver { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if isReturn(field, funcs) { | ||||||
|  | 				if !v.checkReturn { | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 				returnIdents = append(returnIdents, ident) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			paramIdents = append(paramIdents, ident) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	return assignIdents, paramIdents, returnIdents | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // isReceiver returns true when field is a receiver parameter of any of the given methods.
 | ||||||
|  | func isReceiver(field *ast.Field, methods []*ast.FuncDecl) bool { | ||||||
|  | 	for _, m := range methods { | ||||||
|  | 		for _, recv := range m.Recv.List { | ||||||
|  | 			if recv == field { | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // isReturn returns true when field is a return value of any of the given funcs.
 | ||||||
|  | func isReturn(field *ast.Field, funcs []*ast.FuncDecl) bool { | ||||||
|  | 	for _, f := range funcs { | ||||||
|  | 		if f.Type.Results == nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		for _, r := range f.Type.Results.List { | ||||||
|  | 			if r == field { | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Set implements Value.
 | ||||||
|  | func (sv *stringsValue) Set(s string) error { | ||||||
|  | 	sv.Values = strings.Split(s, ",") | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // String implements Value.
 | ||||||
|  | func (sv *stringsValue) String() string { | ||||||
|  | 	return strings.Join(sv.Values, ",") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // contains returns true when sv contains s.
 | ||||||
|  | func (sv *stringsValue) contains(s string) bool { | ||||||
|  | 	for _, v := range sv.Values { | ||||||
|  | 		if v == s { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // isConventional returns true when p is a conventional Go parameter, such as "ctx context.Context" or
 | ||||||
|  | // "t *testing.T".
 | ||||||
|  | func (p parameter) isConventional() bool { //nolint:gocyclo,gocognit
 | ||||||
|  | 	switch { | ||||||
|  | 	case p.name == "t" && p.isPointerType("testing.T"): | ||||||
|  | 		return true | ||||||
|  | 	case p.name == "b" && p.isPointerType("testing.B"): | ||||||
|  | 		return true | ||||||
|  | 	case p.name == "tb" && p.isType("testing.TB"): | ||||||
|  | 		return true | ||||||
|  | 	case p.name == "pb" && p.isPointerType("testing.PB"): | ||||||
|  | 		return true | ||||||
|  | 	case p.name == "m" && p.isPointerType("testing.M"): | ||||||
|  | 		return true | ||||||
|  | 	case p.name == "ctx" && p.isType("context.Context"): | ||||||
|  | 		return true | ||||||
|  | 	default: | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // isType returns true when p is of type typeName.
 | ||||||
|  | func (p parameter) isType(typeName string) bool { | ||||||
|  | 	sel, ok := p.field.Type.(*ast.SelectorExpr) | ||||||
|  | 	if !ok { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return isType(sel, typeName) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // isPointerType returns true when p is a pointer type of type typeName.
 | ||||||
|  | func (p parameter) isPointerType(typeName string) bool { | ||||||
|  | 	star, ok := p.field.Type.(*ast.StarExpr) | ||||||
|  | 	if !ok { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	sel, ok := star.X.(*ast.SelectorExpr) | ||||||
|  | 	if !ok { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return isType(sel, typeName) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // isType returns true when sel is a selector for type typeName.
 | ||||||
|  | func isType(sel *ast.SelectorExpr, typeName string) bool { | ||||||
|  | 	ident, ok := sel.X.(*ast.Ident) | ||||||
|  | 	if !ok { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return typeName == ident.Name+"."+sel.Sel.Name | ||||||
|  | } | ||||||
|  | @ -0,0 +1,70 @@ | ||||||
|  | 
 | ||||||
|  | # Created by https://www.gitignore.io/api/go,vim,macos | ||||||
|  | 
 | ||||||
|  | ### Go ### | ||||||
|  | # 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 | ||||||
|  | 
 | ||||||
|  | ### Go Patch ### | ||||||
|  | /vendor/ | ||||||
|  | /Godeps/ | ||||||
|  | 
 | ||||||
|  | ### macOS ### | ||||||
|  | # General | ||||||
|  | .DS_Store | ||||||
|  | .AppleDouble | ||||||
|  | .LSOverride | ||||||
|  | 
 | ||||||
|  | # Icon must end with two \r | ||||||
|  | Icon | ||||||
|  | 
 | ||||||
|  | # Thumbnails | ||||||
|  | ._* | ||||||
|  | 
 | ||||||
|  | # Files that might appear in the root of a volume | ||||||
|  | .DocumentRevisions-V100 | ||||||
|  | .fseventsd | ||||||
|  | .Spotlight-V100 | ||||||
|  | .TemporaryItems | ||||||
|  | .Trashes | ||||||
|  | .VolumeIcon.icns | ||||||
|  | .com.apple.timemachine.donotpresent | ||||||
|  | 
 | ||||||
|  | # Directories potentially created on remote AFP share | ||||||
|  | .AppleDB | ||||||
|  | .AppleDesktop | ||||||
|  | Network Trash Folder | ||||||
|  | Temporary Items | ||||||
|  | .apdisk | ||||||
|  | 
 | ||||||
|  | ### Vim ### | ||||||
|  | # Swap | ||||||
|  | [._]*.s[a-v][a-z] | ||||||
|  | [._]*.sw[a-p] | ||||||
|  | [._]s[a-rt-v][a-z] | ||||||
|  | [._]ss[a-gi-z] | ||||||
|  | [._]sw[a-p] | ||||||
|  | 
 | ||||||
|  | # Session | ||||||
|  | Session.vim | ||||||
|  | 
 | ||||||
|  | # Temporary | ||||||
|  | .netrwhist | ||||||
|  | *~ | ||||||
|  | # Auto-generated tag files | ||||||
|  | tags | ||||||
|  | # Persistent undo | ||||||
|  | [._]*.un~ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # End of https://www.gitignore.io/api/go,vim,macos | ||||||
|  | @ -0,0 +1,25 @@ | ||||||
|  | --- | ||||||
|  | language: go | ||||||
|  | 
 | ||||||
|  | go: | ||||||
|  |   - 1.13.x | ||||||
|  |   - 1.12.x | ||||||
|  |   - 1.11.x | ||||||
|  | 
 | ||||||
|  | env: | ||||||
|  |   global: | ||||||
|  |     - GO111MODULE=on | ||||||
|  | 
 | ||||||
|  | install: | ||||||
|  |   - go get -v golang.org/x/tools/cmd/cover github.com/mattn/goveralls | ||||||
|  | 
 | ||||||
|  | script: | ||||||
|  |   - go test -v -covermode=count -coverprofile=coverage.out | ||||||
|  | 
 | ||||||
|  | after_script: | ||||||
|  |   - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci | ||||||
|  | 
 | ||||||
|  | notifications: | ||||||
|  |   email: false | ||||||
|  | 
 | ||||||
|  | # vim: set ts=2 sw=2 et: | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | MIT License | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2018 Simon Sawert | ||||||
|  | 
 | ||||||
|  | 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,126 @@ | ||||||
|  | # WSL - Whitespace Linter | ||||||
|  | 
 | ||||||
|  | [](https://forthebadge.com) | ||||||
|  | [](https://forthebadge.com) | ||||||
|  | 
 | ||||||
|  | [](https://travis-ci.org/bombsimon/wsl) | ||||||
|  | [](https://coveralls.io/github/bombsimon/wsl?branch=master) | ||||||
|  | 
 | ||||||
|  | WSL is a linter that enforces a very **non scientific** vision of how to make | ||||||
|  | code more readable by enforcing empty lines at the right places. | ||||||
|  | 
 | ||||||
|  | I think too much code out there is to cuddly and a bit too warm for it's own | ||||||
|  | good, making it harder for other people to read and understand. The linter will | ||||||
|  | warn about newlines in and around blocks, in the beginning of files and other | ||||||
|  | places in the code. | ||||||
|  | 
 | ||||||
|  | **I know this linter is aggressive** and a lot of projects I've tested it on | ||||||
|  | have failed miserably. For this linter to be useful at all I want to be open to | ||||||
|  | new ideas, configurations and discussions! Also note that some of the warnings | ||||||
|  | might be bugs or unintentional false positives so I would love an | ||||||
|  | [issue](https://github.com/bombsimon/wsl/issues/new) to fix, discuss, change or | ||||||
|  | make something configurable! | ||||||
|  | 
 | ||||||
|  | ## Installation | ||||||
|  | 
 | ||||||
|  | ### By `go get` (local installation) | ||||||
|  | 
 | ||||||
|  | You can do that by using: | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | go get -u github.com/bombsimon/wsl/cmd/... | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### By golangci-lint (CI automation) | ||||||
|  | 
 | ||||||
|  | `wsl` is already integrated with | ||||||
|  | [golangci-lint](https://github.com/golangci/golangci-lint). Please refer to the | ||||||
|  | instructions there. | ||||||
|  | 
 | ||||||
|  | ## Usage | ||||||
|  | 
 | ||||||
|  | How to use depends on how you install `wsl`. | ||||||
|  | 
 | ||||||
|  | ### With local binary | ||||||
|  | 
 | ||||||
|  | The general command format for `wsl` is: | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | $ wsl [flags] <file1> [files...] | ||||||
|  | $ wsl [flags] </path/to/package/...> | ||||||
|  | 
 | ||||||
|  | # Examples | ||||||
|  | 
 | ||||||
|  | $ wsl ./main.go | ||||||
|  | $ wsl --no-test ./main.go | ||||||
|  | $ wsl --allow-cuddle-declarations ./main.go | ||||||
|  | $ wsl --no-test --allow-cuddle-declaration ./main.go | ||||||
|  | $ wsl --no-test --allow-trailing-comment ./myProject/... | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The "..." wildcard is not used like other `go` commands but instead can only | ||||||
|  | be to a relative or absolute path. | ||||||
|  | 
 | ||||||
|  | By default, the linter will run on `./...` which means all go files in the | ||||||
|  | current path and all subsequent paths, including test files. To disable linting | ||||||
|  | test files, use `-n` or `--no-test`. | ||||||
|  | 
 | ||||||
|  | ### By `golangci-lint` (CI automation) | ||||||
|  | 
 | ||||||
|  | The recommended command is: | ||||||
|  | 
 | ||||||
|  | ```sh | ||||||
|  | golangci-lint run --disable-all --enable wsl | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | For more information, please refer to | ||||||
|  | [golangci-lint](https://github.com/golangci/golangci-lint)'s documentation. | ||||||
|  | 
 | ||||||
|  | ## Issues and configuration | ||||||
|  | 
 | ||||||
|  | The linter suppers a few ways to configure it to satisfy more than one kind of | ||||||
|  | code style. These settings could be set either with flags or with YAML | ||||||
|  | configuration if used via `golangci-lint`. | ||||||
|  | 
 | ||||||
|  | The supported configuration can be found [in the documentation](doc/configuration.md). | ||||||
|  | 
 | ||||||
|  | Below are the available checklist for any hit from `wsl`. If you do not see any, | ||||||
|  | feel free to raise an [issue](https://github.com/bombsimon/wsl/issues/new). | ||||||
|  | 
 | ||||||
|  | > **Note**:  this linter doesn't take in consideration the issues that will be | ||||||
|  | > fixed with `go fmt -s` so ensure that the code is properly formatted before | ||||||
|  | > use. | ||||||
|  | 
 | ||||||
|  | * [Anonymous switch statements should never be cuddled](doc/rules.md#anonymous-switch-statements-should-never-be-cuddled) | ||||||
|  | * [Append only allowed to cuddle with appended value](doc/rules.md#append-only-allowed-to-cuddle-with-appended-value) | ||||||
|  | * [Assignments should only be cuddled with other assignments](doc/rules.md#assignments-should-only-be-cuddled-with-other-assignments) | ||||||
|  | * [Block should not end with a whitespace (or comment)](doc/rules.md#block-should-not-end-with-a-whitespace-or-comment) | ||||||
|  | * [Block should not start with a whitespace](doc/rules.md#block-should-not-start-with-a-whitespace) | ||||||
|  | * [Case block should end with newline at this size](doc/rules.md#case-block-should-end-with-newline-at-this-size) | ||||||
|  | * [Branch statements should not be cuddled if block has more than two lines](doc/rules.md#branch-statements-should-not-be-cuddled-if-block-has-more-than-two-lines) | ||||||
|  | * [Declarations should never be cuddled](doc/rules.md#declarations-should-never-be-cuddled) | ||||||
|  | * [Defer statements should only be cuddled with expressions on same variable](doc/rules.md#defer-statements-should-only-be-cuddled-with-expressions-on-same-variable) | ||||||
|  | * [Expressions should not be cuddled with blocks](doc/rules.md#expressions-should-not-be-cuddled-with-blocks) | ||||||
|  | * [Expressions should not be cuddled with declarations or returns](doc/rules.md#expressions-should-not-be-cuddled-with-declarations-or-returns) | ||||||
|  | * [For statement without condition should never be cuddled](doc/rules.md#for-statement-without-condition-should-never-be-cuddled) | ||||||
|  | * [For statements should only be cuddled with assignments used in the iteration](doc/rules.md#for-statements-should-only-be-cuddled-with-assignments-used-in-the-iteration) | ||||||
|  | * [Go statements can only invoke functions assigned on line above](doc/rules.md#go-statements-can-only-invoke-functions-assigned-on-line-above) | ||||||
|  | * [If statements should only be cuddled with assignments](doc/rules.md#if-statements-should-only-be-cuddled-with-assignments) | ||||||
|  | * [If statements should only be cuddled with assignments used in the if | ||||||
|  |   statement | ||||||
|  |   itself](doc/rules.md#if-statements-should-only-be-cuddled-with-assignments-used-in-the-if-statement-itself) | ||||||
|  | * [If statements that check an error must be cuddled with the statement that assigned the error](doc/rules.md#if-statements-that-check-an-error-must-be-cuddled-with-the-statement-that-assigned-the-error) | ||||||
|  | * [Only cuddled expressions if assigning variable or using from line | ||||||
|  |   above](doc/rules.md#only-cuddled-expressions-if-assigning-variable-or-using-from-line-above) | ||||||
|  | * [Only one cuddle assignment allowed before defer statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-defer-statement) | ||||||
|  | * [Only one cuddle assginment allowed before for statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-for-statement) | ||||||
|  | * [Only one cuddle assignment allowed before go statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-go-statement) | ||||||
|  | * [Only one cuddle assignment allowed before if statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-if-statement) | ||||||
|  | * [Only one cuddle assignment allowed before range statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-range-statement) | ||||||
|  | * [Only one cuddle assignment allowed before switch statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-switch-statement) | ||||||
|  | * [Only one cuddle assignment allowed before type switch statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-type-switch-statement) | ||||||
|  | * [Ranges should only be cuddled with assignments used in the iteration](doc/rules.md#ranges-should-only-be-cuddled-with-assignments-used-in-the-iteration) | ||||||
|  | * [Return statements should not be cuddled if block has more than two lines](doc/rules.md#return-statements-should-not-be-cuddled-if-block-has-more-than-two-lines) | ||||||
|  | * [Short declarations should cuddle only with other short declarations](doc/rules.md#short-declaration-should-cuddle-only-with-other-short-declarations) | ||||||
|  | * [Switch statements should only be cuddled with variables switched](doc/rules.md#switch-statements-should-only-be-cuddled-with-variables-switched) | ||||||
|  | * [Type switch statements should only be cuddled with variables switched](doc/rules.md#type-switch-statements-should-only-be-cuddled-with-variables-switched) | ||||||
|  | @ -0,0 +1,12 @@ | ||||||
|  | module github.com/bombsimon/wsl/v3 | ||||||
|  | 
 | ||||||
|  | go 1.12 | ||||||
|  | 
 | ||||||
|  | require ( | ||||||
|  | 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||||
|  | 	github.com/kr/text v0.2.0 // indirect | ||||||
|  | 	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect | ||||||
|  | 	github.com/stretchr/testify v1.5.1 | ||||||
|  | 	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect | ||||||
|  | 	gopkg.in/yaml.v2 v2.2.8 // indirect | ||||||
|  | ) | ||||||
|  | @ -0,0 +1,25 @@ | ||||||
|  | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | ||||||
|  | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | ||||||
|  | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
|  | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||||
|  | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
|  | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||||
|  | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= | ||||||
|  | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||||
|  | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||||||
|  | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||||
|  | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= | ||||||
|  | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | ||||||
|  | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||||
|  | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
|  | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
|  | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= | ||||||
|  | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | ||||||
|  | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||||||
|  | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
|  | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= | ||||||
|  | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
|  | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= | ||||||
|  | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
|  | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= | ||||||
|  | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | MIT License | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2021 Lucas Bremgartner | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
							
								
								
									
										67
									
								
								tests/tools/vendor/github.com/breml/bidichk/pkg/bidichk/bidichk.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										67
									
								
								tests/tools/vendor/github.com/breml/bidichk/pkg/bidichk/bidichk.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,67 @@ | ||||||
|  | package bidichk | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"go/token" | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
|  | 	"unicode/utf8" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/go/analysis" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var Analyzer = &analysis.Analyzer{ | ||||||
|  | 	Name: "bidichk", | ||||||
|  | 	Doc:  "Checks for dangerous unicode character sequences", | ||||||
|  | 	Run:  run, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func run(pass *analysis.Pass) (interface{}, error) { | ||||||
|  | 	var err error | ||||||
|  | 
 | ||||||
|  | 	pass.Fset.Iterate(func(f *token.File) bool { | ||||||
|  | 		if strings.HasPrefix(f.Name(), "$GOROOT") { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return check(f.Name(), f.Pos(0), pass) == nil | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	return nil, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var disallowedRunes = map[string]rune{ | ||||||
|  | 	"LEFT-TO-RIGHT-EMBEDDING":    '\u202A', | ||||||
|  | 	"RIGHT-TO-LEFT-EMBEDDING":    '\u202B', | ||||||
|  | 	"POP-DIRECTIONAL-FORMATTING": '\u202C', | ||||||
|  | 	"LEFT-TO-RIGHT-OVERRIDE":     '\u202D', | ||||||
|  | 	"RIGHT-TO-LEFT-OVERRIDE":     '\u202E', | ||||||
|  | 	"LEFT-TO-RIGHT-ISOLATE":      '\u2066', | ||||||
|  | 	"RIGHT-TO-LEFT-ISOLATE":      '\u2067', | ||||||
|  | 	"FIRST-STRONG-ISOLATE":       '\u2068', | ||||||
|  | 	"POP-DIRECTIONAL-ISOLATE":    '\u2069', | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func check(filename string, pos token.Pos, pass *analysis.Pass) error { | ||||||
|  | 	body, err := os.ReadFile(filename) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for name, r := range disallowedRunes { | ||||||
|  | 		start := 0 | ||||||
|  | 		for { | ||||||
|  | 			idx := bytes.IndexRune(body[start:], r) | ||||||
|  | 			if idx == -1 { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			start += idx | ||||||
|  | 
 | ||||||
|  | 			pass.Reportf(pos+token.Pos(start), "found dangerous unicode character sequence %s", name) | ||||||
|  | 
 | ||||||
|  | 			start += utf8.RuneLen(r) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | MIT License | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2021 Oleg Butuzov | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
							
								
								
									
										193
									
								
								tests/tools/vendor/github.com/butuzov/ireturn/analyzer/analyzer.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										193
									
								
								tests/tools/vendor/github.com/butuzov/ireturn/analyzer/analyzer.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,193 @@ | ||||||
|  | package analyzer | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"flag" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/ast" | ||||||
|  | 	gotypes "go/types" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"github.com/butuzov/ireturn/config" | ||||||
|  | 	"github.com/butuzov/ireturn/types" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/tools/go/analysis" | ||||||
|  | 	"golang.org/x/tools/go/analysis/passes/inspect" | ||||||
|  | 	"golang.org/x/tools/go/ast/inspector" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const name string = "ireturn" // linter name
 | ||||||
|  | 
 | ||||||
|  | type validator interface { | ||||||
|  | 	IsValid(types.IFace) bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type analyzer struct { | ||||||
|  | 	once    sync.Once | ||||||
|  | 	handler validator | ||||||
|  | 	err     error | ||||||
|  | 
 | ||||||
|  | 	found []analysis.Diagnostic | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) { | ||||||
|  | 	// 00. Part 1. Handling Configuration Only Once.
 | ||||||
|  | 	a.once.Do(func() { a.readConfiguration(&pass.Analyzer.Flags) }) | ||||||
|  | 
 | ||||||
|  | 	// 00. Part 2. Handling Errors
 | ||||||
|  | 	if a.err != nil { | ||||||
|  | 		return nil, a.err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 01. Running Inspection.
 | ||||||
|  | 	ins, _ := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) | ||||||
|  | 	ins.Preorder([]ast.Node{(*ast.FuncDecl)(nil)}, func(node ast.Node) { | ||||||
|  | 		// 001. Casting to funcdecl
 | ||||||
|  | 		f, _ := node.(*ast.FuncDecl) | ||||||
|  | 
 | ||||||
|  | 		// 002. Does it return any results ?
 | ||||||
|  | 		if f.Type == nil || f.Type.Results == nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// 003. Is it allowed to be checked?
 | ||||||
|  | 		// TODO(butuzov): add inline comment
 | ||||||
|  | 		if hasDisallowDirective(f.Doc) { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// 004. Filtering Results.
 | ||||||
|  | 		for _, i := range filterInterfaces(pass, f.Type.Results) { | ||||||
|  | 
 | ||||||
|  | 			if a.handler.IsValid(i) { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			a.found = append(a.found, analysis.Diagnostic{ //nolint: exhaustivestruct
 | ||||||
|  | 				Pos:     f.Pos(), | ||||||
|  | 				Message: fmt.Sprintf("%s returns interface (%s)", f.Name.Name, i.Name), | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	// 02. Printing reports.
 | ||||||
|  | 	for i := range a.found { | ||||||
|  | 		pass.Report(a.found[i]) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *analyzer) readConfiguration(fs *flag.FlagSet) { | ||||||
|  | 	cnf, err := config.New(fs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		a.err = err | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if validatorImpl, ok := cnf.(validator); ok { | ||||||
|  | 		a.handler = validatorImpl | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	a.handler = config.DefaultValidatorConfig() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewAnalyzer() *analysis.Analyzer { | ||||||
|  | 	a := analyzer{} //nolint: exhaustivestruct
 | ||||||
|  | 
 | ||||||
|  | 	return &analysis.Analyzer{ | ||||||
|  | 		Name:     name, | ||||||
|  | 		Doc:      "Accept Interfaces, Return Concrete Types", | ||||||
|  | 		Run:      a.run, | ||||||
|  | 		Requires: []*analysis.Analyzer{inspect.Analyzer}, | ||||||
|  | 		Flags:    flags(), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func flags() flag.FlagSet { | ||||||
|  | 	set := flag.NewFlagSet("", flag.PanicOnError) | ||||||
|  | 	set.String("allow", "", "accept-list of the comma-separated interfaces") | ||||||
|  | 	set.String("reject", "", "reject-list of the comma-separated interfaces") | ||||||
|  | 	return *set | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func filterInterfaces(pass *analysis.Pass, fl *ast.FieldList) []types.IFace { | ||||||
|  | 	var results []types.IFace | ||||||
|  | 
 | ||||||
|  | 	for pos, el := range fl.List { | ||||||
|  | 		switch v := el.Type.(type) { | ||||||
|  | 		// ----- empty or anonymous interfaces
 | ||||||
|  | 		case *ast.InterfaceType: | ||||||
|  | 
 | ||||||
|  | 			if len(v.Methods.List) == 0 { | ||||||
|  | 				results = append(results, issue("interface{}", pos, types.EmptyInterface)) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			results = append(results, issue("anonymous interface", pos, types.AnonInterface)) | ||||||
|  | 
 | ||||||
|  | 		// ------ Errors and interfaces from same package
 | ||||||
|  | 		case *ast.Ident: | ||||||
|  | 
 | ||||||
|  | 			t1 := pass.TypesInfo.TypeOf(el.Type) | ||||||
|  | 			if !gotypes.IsInterface(t1.Underlying()) { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			word := t1.String() | ||||||
|  | 			// only build in interface is error
 | ||||||
|  | 			if obj := gotypes.Universe.Lookup(word); obj != nil { | ||||||
|  | 				results = append(results, issue(obj.Name(), pos, types.ErrorInterface)) | ||||||
|  | 
 | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			results = append(results, issue(word, pos, types.NamedInterface)) | ||||||
|  | 
 | ||||||
|  | 		// ------- standard library and 3rd party interfaces
 | ||||||
|  | 		case *ast.SelectorExpr: | ||||||
|  | 
 | ||||||
|  | 			t1 := pass.TypesInfo.TypeOf(el.Type) | ||||||
|  | 			if !gotypes.IsInterface(t1.Underlying()) { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			word := t1.String() | ||||||
|  | 			if isStdLib(word) { | ||||||
|  | 				results = append(results, issue(word, pos, types.NamedStdInterface)) | ||||||
|  | 
 | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			results = append(results, issue(word, pos, types.NamedInterface)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return results | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // isStdLib will run small checks against pkg to find out if  named interface
 | ||||||
|  | // we lookling on comes from a standard library or not.
 | ||||||
|  | func isStdLib(named string) bool { | ||||||
|  | 	// find last dot index.
 | ||||||
|  | 	idx := strings.LastIndex(named, ".") | ||||||
|  | 	if idx == -1 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if _, ok := std[named[0:idx]]; ok { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // issue is shortcut that creates issue for next filtering.
 | ||||||
|  | func issue(name string, pos int, interfaceType types.IType) types.IFace { | ||||||
|  | 	return types.IFace{ | ||||||
|  | 		Name: name, | ||||||
|  | 		Pos:  pos, | ||||||
|  | 		Type: interfaceType, | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										45
									
								
								tests/tools/vendor/github.com/butuzov/ireturn/analyzer/disallow.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										45
									
								
								tests/tools/vendor/github.com/butuzov/ireturn/analyzer/disallow.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,45 @@ | ||||||
|  | package analyzer | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"go/ast" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const nolintPrefix = "//nolint" | ||||||
|  | 
 | ||||||
|  | func hasDisallowDirective(cg *ast.CommentGroup) bool { | ||||||
|  | 	if cg == nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return directiveFound(cg) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func directiveFound(cg *ast.CommentGroup) bool { | ||||||
|  | 	for i := len(cg.List) - 1; i >= 0; i-- { | ||||||
|  | 		comment := cg.List[i] | ||||||
|  | 		if !strings.HasPrefix(comment.Text, nolintPrefix) { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		startingIdx := len(nolintPrefix) | ||||||
|  | 		for { | ||||||
|  | 			idx := strings.Index(comment.Text[startingIdx:], name) | ||||||
|  | 			if idx == -1 { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if len(comment.Text[startingIdx+idx:]) == len(name) { | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			c := comment.Text[startingIdx+idx+len(name)] | ||||||
|  | 			if c == '.' || c == ',' || c == ' ' || c == '	' { | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 			startingIdx += idx + 1 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | @ -0,0 +1,186 @@ | ||||||
|  | // Code generated using std.sh; DO NOT EDIT.
 | ||||||
|  | 
 | ||||||
|  | // We will ignore that fact that some of packages
 | ||||||
|  | // were removed from stdlib.
 | ||||||
|  | 
 | ||||||
|  | package analyzer | ||||||
|  | 
 | ||||||
|  | var std = map[string]struct{}{ | ||||||
|  | 	// added in Go v1.2 in compare to v1.1 (docker image)
 | ||||||
|  | 	"archive/tar":         {}, | ||||||
|  | 	"archive/zip":         {}, | ||||||
|  | 	"bufio":               {}, | ||||||
|  | 	"bytes":               {}, | ||||||
|  | 	"cmd/cgo":             {}, | ||||||
|  | 	"cmd/fix":             {}, | ||||||
|  | 	"cmd/go":              {}, | ||||||
|  | 	"cmd/gofmt":           {}, | ||||||
|  | 	"cmd/yacc":            {}, | ||||||
|  | 	"compress/bzip2":      {}, | ||||||
|  | 	"compress/flate":      {}, | ||||||
|  | 	"compress/gzip":       {}, | ||||||
|  | 	"compress/lzw":        {}, | ||||||
|  | 	"compress/zlib":       {}, | ||||||
|  | 	"container/heap":      {}, | ||||||
|  | 	"container/list":      {}, | ||||||
|  | 	"container/ring":      {}, | ||||||
|  | 	"crypto":              {}, | ||||||
|  | 	"crypto/aes":          {}, | ||||||
|  | 	"crypto/cipher":       {}, | ||||||
|  | 	"crypto/des":          {}, | ||||||
|  | 	"crypto/dsa":          {}, | ||||||
|  | 	"crypto/ecdsa":        {}, | ||||||
|  | 	"crypto/elliptic":     {}, | ||||||
|  | 	"crypto/hmac":         {}, | ||||||
|  | 	"crypto/md5":          {}, | ||||||
|  | 	"crypto/rand":         {}, | ||||||
|  | 	"crypto/rc4":          {}, | ||||||
|  | 	"crypto/rsa":          {}, | ||||||
|  | 	"crypto/sha1":         {}, | ||||||
|  | 	"crypto/sha256":       {}, | ||||||
|  | 	"crypto/sha512":       {}, | ||||||
|  | 	"crypto/subtle":       {}, | ||||||
|  | 	"crypto/tls":          {}, | ||||||
|  | 	"crypto/x509":         {}, | ||||||
|  | 	"crypto/x509/pkix":    {}, | ||||||
|  | 	"database/sql":        {}, | ||||||
|  | 	"database/sql/driver": {}, | ||||||
|  | 	"debug/dwarf":         {}, | ||||||
|  | 	"debug/elf":           {}, | ||||||
|  | 	"debug/gosym":         {}, | ||||||
|  | 	"debug/macho":         {}, | ||||||
|  | 	"debug/pe":            {}, | ||||||
|  | 	"encoding":            {}, | ||||||
|  | 	"encoding/ascii85":    {}, | ||||||
|  | 	"encoding/asn1":       {}, | ||||||
|  | 	"encoding/base32":     {}, | ||||||
|  | 	"encoding/base64":     {}, | ||||||
|  | 	"encoding/binary":     {}, | ||||||
|  | 	"encoding/csv":        {}, | ||||||
|  | 	"encoding/gob":        {}, | ||||||
|  | 	"encoding/hex":        {}, | ||||||
|  | 	"encoding/json":       {}, | ||||||
|  | 	"encoding/pem":        {}, | ||||||
|  | 	"encoding/xml":        {}, | ||||||
|  | 	"errors":              {}, | ||||||
|  | 	"expvar":              {}, | ||||||
|  | 	"flag":                {}, | ||||||
|  | 	"fmt":                 {}, | ||||||
|  | 	"go/ast":              {}, | ||||||
|  | 	"go/build":            {}, | ||||||
|  | 	"go/doc":              {}, | ||||||
|  | 	"go/format":           {}, | ||||||
|  | 	"go/parser":           {}, | ||||||
|  | 	"go/printer":          {}, | ||||||
|  | 	"go/scanner":          {}, | ||||||
|  | 	"go/token":            {}, | ||||||
|  | 	"hash":                {}, | ||||||
|  | 	"hash/adler32":        {}, | ||||||
|  | 	"hash/crc32":          {}, | ||||||
|  | 	"hash/crc64":          {}, | ||||||
|  | 	"hash/fnv":            {}, | ||||||
|  | 	"html":                {}, | ||||||
|  | 	"html/template":       {}, | ||||||
|  | 	"image":               {}, | ||||||
|  | 	"image/color":         {}, | ||||||
|  | 	"image/color/palette": {}, | ||||||
|  | 	"image/draw":          {}, | ||||||
|  | 	"image/gif":           {}, | ||||||
|  | 	"image/jpeg":          {}, | ||||||
|  | 	"image/png":           {}, | ||||||
|  | 	"index/suffixarray":   {}, | ||||||
|  | 	"io":                  {}, | ||||||
|  | 	"io/ioutil":           {}, | ||||||
|  | 	"log":                 {}, | ||||||
|  | 	"log/syslog":          {}, | ||||||
|  | 	"math":                {}, | ||||||
|  | 	"math/big":            {}, | ||||||
|  | 	"math/cmplx":          {}, | ||||||
|  | 	"math/rand":           {}, | ||||||
|  | 	"mime":                {}, | ||||||
|  | 	"mime/multipart":      {}, | ||||||
|  | 	"net":                 {}, | ||||||
|  | 	"net/http":            {}, | ||||||
|  | 	"net/http/cgi":        {}, | ||||||
|  | 	"net/http/cookiejar":  {}, | ||||||
|  | 	"net/http/fcgi":       {}, | ||||||
|  | 	"net/http/httptest":   {}, | ||||||
|  | 	"net/http/httputil":   {}, | ||||||
|  | 	"net/http/pprof":      {}, | ||||||
|  | 	"net/mail":            {}, | ||||||
|  | 	"net/rpc":             {}, | ||||||
|  | 	"net/rpc/jsonrpc":     {}, | ||||||
|  | 	"net/smtp":            {}, | ||||||
|  | 	"net/textproto":       {}, | ||||||
|  | 	"net/url":             {}, | ||||||
|  | 	"os":                  {}, | ||||||
|  | 	"os/exec":             {}, | ||||||
|  | 	"os/signal":           {}, | ||||||
|  | 	"os/user":             {}, | ||||||
|  | 	"path":                {}, | ||||||
|  | 	"path/filepath":       {}, | ||||||
|  | 	"reflect":             {}, | ||||||
|  | 	"regexp":              {}, | ||||||
|  | 	"regexp/syntax":       {}, | ||||||
|  | 	"runtime":             {}, | ||||||
|  | 	"runtime/cgo":         {}, | ||||||
|  | 	"runtime/debug":       {}, | ||||||
|  | 	"runtime/pprof":       {}, | ||||||
|  | 	"runtime/race":        {}, | ||||||
|  | 	"sort":                {}, | ||||||
|  | 	"strconv":             {}, | ||||||
|  | 	"strings":             {}, | ||||||
|  | 	"sync":                {}, | ||||||
|  | 	"sync/atomic":         {}, | ||||||
|  | 	"syscall":             {}, | ||||||
|  | 	"testing":             {}, | ||||||
|  | 	"testing/iotest":      {}, | ||||||
|  | 	"testing/quick":       {}, | ||||||
|  | 	"text/scanner":        {}, | ||||||
|  | 	"text/tabwriter":      {}, | ||||||
|  | 	"text/template":       {}, | ||||||
|  | 	"text/template/parse": {}, | ||||||
|  | 	"time":                {}, | ||||||
|  | 	"unicode":             {}, | ||||||
|  | 	"unicode/utf16":       {}, | ||||||
|  | 	"unicode/utf8":        {}, | ||||||
|  | 	"unsafe":              {}, | ||||||
|  | 	// added in Go v1.3 in compare to v1.2 (docker image)
 | ||||||
|  | 	"cmd/addr2line":  {}, | ||||||
|  | 	"cmd/nm":         {}, | ||||||
|  | 	"cmd/objdump":    {}, | ||||||
|  | 	"cmd/pack":       {}, | ||||||
|  | 	"debug/plan9obj": {}, | ||||||
|  | 	// added in Go v1.4 in compare to v1.3 (docker image)
 | ||||||
|  | 	"cmd/pprof": {}, | ||||||
|  | 	// added in Go v1.5 in compare to v1.4 (docker image)
 | ||||||
|  | 	"go/constant":          {}, | ||||||
|  | 	"go/importer":          {}, | ||||||
|  | 	"go/types":             {}, | ||||||
|  | 	"mime/quotedprintable": {}, | ||||||
|  | 	"runtime/trace":        {}, | ||||||
|  | 	// added in Go v1.6 in compare to v1.5 (docker image)
 | ||||||
|  | 	// added in Go v1.7 in compare to v1.6 (docker image)
 | ||||||
|  | 	"context":            {}, | ||||||
|  | 	"net/http/httptrace": {}, | ||||||
|  | 	// added in Go v1.8 in compare to v1.7 (docker image)
 | ||||||
|  | 	"plugin": {}, | ||||||
|  | 	// added in Go v1.9 in compare to v1.8 (docker image)
 | ||||||
|  | 	"math/bits": {}, | ||||||
|  | 	// added in Go v1.10 in compare to v1.9 (docker image)
 | ||||||
|  | 	// added in Go v1.11 in compare to v1.10 (docker image)
 | ||||||
|  | 	// added in Go v1.12 in compare to v1.11 (docker image)
 | ||||||
|  | 	// added in Go v1.13 in compare to v1.12 (docker image)
 | ||||||
|  | 	"crypto/ed25519": {}, | ||||||
|  | 	// added in Go v1.14 in compare to v1.13 (docker image)
 | ||||||
|  | 	"hash/maphash": {}, | ||||||
|  | 	// added in Go v1.15 in compare to v1.14 (docker image)
 | ||||||
|  | 	"time/tzdata": {}, | ||||||
|  | 	// added in Go v1.16 in compare to v1.15 (docker image)
 | ||||||
|  | 	"embed":               {}, | ||||||
|  | 	"go/build/constraint": {}, | ||||||
|  | 	"io/fs":               {}, | ||||||
|  | 	"runtime/metrics":     {}, | ||||||
|  | 	"testing/fstest":      {}, | ||||||
|  | 	// added in Go v1.17 in compare to v1.16 (docker image)
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | package config | ||||||
|  | 
 | ||||||
|  | import "github.com/butuzov/ireturn/types" | ||||||
|  | 
 | ||||||
|  | // allowConfig specifies a list of interfaces (keywords, patters and regular expressions)
 | ||||||
|  | // that are allowed by ireturn as valid to return, any non listed interface are rejected.
 | ||||||
|  | type allowConfig struct { | ||||||
|  | 	*defaultConfig | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func allowAll(patterns []string) *allowConfig { | ||||||
|  | 	return &allowConfig{&defaultConfig{List: patterns}} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ac *allowConfig) IsValid(i types.IFace) bool { | ||||||
|  | 	return ac.Has(i) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,66 @@ | ||||||
|  | package config | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"regexp" | ||||||
|  | 
 | ||||||
|  | 	"github.com/butuzov/ireturn/types" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // defaultConfig is core of the validation, ...
 | ||||||
|  | // todo(butuzov): write proper intro...
 | ||||||
|  | 
 | ||||||
|  | type defaultConfig struct { | ||||||
|  | 	List []string | ||||||
|  | 
 | ||||||
|  | 	// private fields (for search optimization look ups)
 | ||||||
|  | 	init  bool | ||||||
|  | 	quick uint8 | ||||||
|  | 	list  []*regexp.Regexp | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (config *defaultConfig) Has(i types.IFace) bool { | ||||||
|  | 	if !config.init { | ||||||
|  | 		config.compileList() | ||||||
|  | 		config.init = true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if config.quick&uint8(i.Type) > 0 { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// not a named interface (because error, interface{}, anon interface has keywords.)
 | ||||||
|  | 	if i.Type&types.NamedInterface == 0 && i.Type&types.NamedStdInterface == 0 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, re := range config.list { | ||||||
|  | 		if re.MatchString(i.Name) { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // compileList will transform text list into a bitmask for quick searches and
 | ||||||
|  | // slice of regular expressions for quick searches.
 | ||||||
|  | func (config *defaultConfig) compileList() { | ||||||
|  | 	for _, str := range config.List { | ||||||
|  | 		switch str { | ||||||
|  | 		case types.NameError: | ||||||
|  | 			config.quick |= uint8(types.ErrorInterface) | ||||||
|  | 		case types.NameEmpty: | ||||||
|  | 			config.quick |= uint8(types.EmptyInterface) | ||||||
|  | 		case types.NameAnon: | ||||||
|  | 			config.quick |= uint8(types.AnonInterface) | ||||||
|  | 		case types.NameStdLib: | ||||||
|  | 			config.quick |= uint8(types.NamedStdInterface) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// allow to parse regular expressions
 | ||||||
|  | 		// todo(butuzov): how can we log error in golangci-lint?
 | ||||||
|  | 		if re, err := regexp.Compile(str); err == nil { | ||||||
|  | 			config.list = append(config.list, re) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,74 @@ | ||||||
|  | package config | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"flag" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/butuzov/ireturn/types" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ErrCollisionOfInterests = errors.New("can't have both `-accept` and `-reject` specified at same time") | ||||||
|  | 
 | ||||||
|  | //nolint: exhaustivestruct
 | ||||||
|  | func DefaultValidatorConfig() *allowConfig { | ||||||
|  | 	return allowAll([]string{ | ||||||
|  | 		types.NameEmpty,  // "empty": empty interfaces (interface{})
 | ||||||
|  | 		types.NameError,  // "error": for all error's
 | ||||||
|  | 		types.NameAnon,   // "anon": for all empty interfaces with methods (interface {Method()})
 | ||||||
|  | 		types.NameStdLib, // "std": for all standard library packages
 | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // New is factory function that return allowConfig or rejectConfig depending
 | ||||||
|  | // on provided arguments.
 | ||||||
|  | func New(fs *flag.FlagSet) (interface{}, error) { | ||||||
|  | 	var ( | ||||||
|  | 		allowList  = toSlice(getFlagVal(fs, "allow")) | ||||||
|  | 		rejectList = toSlice(getFlagVal(fs, "reject")) | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	// can't have both at same time.
 | ||||||
|  | 	if len(allowList) != 0 && len(rejectList) != 0 { | ||||||
|  | 		return nil, ErrCollisionOfInterests | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch { | ||||||
|  | 	case len(allowList) > 0: | ||||||
|  | 		return allowAll(allowList), nil | ||||||
|  | 	case len(rejectList) > 0: | ||||||
|  | 		return rejectAll(rejectList), nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// can have none at same time.
 | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // both constants used to cleanup items provided in comma separated list.
 | ||||||
|  | const ( | ||||||
|  | 	SepTab   string = " " | ||||||
|  | 	SepSpace string = "	" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func toSlice(s string) []string { | ||||||
|  | 	var results []string | ||||||
|  | 
 | ||||||
|  | 	for _, pattern := range strings.Split(s, ",") { | ||||||
|  | 		pattern = strings.Trim(pattern, SepTab+SepSpace) | ||||||
|  | 		if pattern != "" { | ||||||
|  | 			results = append(results, pattern) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return results | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getFlagVal(fs *flag.FlagSet, name string) string { | ||||||
|  | 	flg := fs.Lookup(name) | ||||||
|  | 
 | ||||||
|  | 	if flg == nil { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return flg.Value.String() | ||||||
|  | } | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | package config | ||||||
|  | 
 | ||||||
|  | import "github.com/butuzov/ireturn/types" | ||||||
|  | 
 | ||||||
|  | // rejectConfig specifies a list of interfaces (keywords, patters and regular expressions)
 | ||||||
|  | // that are rejected by ireturn as valid to return, any non listed interface are allowed.
 | ||||||
|  | type rejectConfig struct { | ||||||
|  | 	*defaultConfig | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func rejectAll(patterns []string) *rejectConfig { | ||||||
|  | 	return &rejectConfig{&defaultConfig{List: patterns}} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (rc *rejectConfig) IsValid(i types.IFace) bool { | ||||||
|  | 	return !rc.Has(i) | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue