[release-branch.go1.7] all: merge master into release-branch.go1.7
72064bd
cmd/heapview: dowgrade to customelements v0337c012
x/tools/cmd/heapview: add a heading to the page7ef02fd
cmd/heapview: add karma+jasmine TS unit testing configbf0c35b
tip/doc: update doc to match latest gcloud syntax9e74590
x/tools/cmd/heapview: break out init code053ddb9
x/tools/cmd/heapview: add basic client serving1c6f639
imports: don't ignore GOPATH if GOROOT is a prefix of GOPATHed69e84
cmd/goimports, imports: add -local flagf328430
cmd/goimports: permit complete filename for -srcdir5b59ce8
cmd/bundle: More idiomatic flag.Usage.682b241
imports: make filepath.Rel work on windows55296b3
x/tools/cmd/heapview: rename heapdump to heapview1634796
go/ssa: fix stale docs for CreateProgram and Build5dbb806
x/tools/cmd/heapdump: add empty heapdump commandd4a8e58
imports: skip "node_modules" directoriesedf8e6f
cmd/goimports, imports: optimize directory scanning and other thingscaebc7a
imports: ignore case and hyphens when lexically filtering candidate dirsb825d28
cmd/oracle: announce planned deletion in 2.5 monthsf2932db
cmd/guru: suppress failing test on plan929481a3
imports: skip test on plan9681db09
godoc/static: use window scope for checking notesEnabled6d32be8
imports: minor fixesc550f66
go/gcimporter15: backport of https://golang.org/cl/23606/3f933d4
go/gcimporter15: backport of https://golang.org/cl/23012/ffe4e61
imports: add configuration mechanism to exclude directories7c26c99
imports: do less I/O finding the package name of an importef6b6eb
cmd/guru: what: include imported package names in sameids92480e4
cmd/guru: update Emacs installation documentation9c3986d
refactor/rename: fix two bugs related to MS Windows' path separatore047ae7
cmd/goimports, imports: make goimports great againb3887f7
refactor/rename: fix spurious conflict report when renaming x/foo to y/foo0835c73
imports: special case rand.Read, prevent math/rand by chancecda5280
cmd/guru: fix quoting bug in Emacs binding130914b
tip: update package doc to refer to stable code35c6e68
cmd/guru: update link to documentation2b32496
cmd/guru: add menu to Emacs Change-Id: I8d32f370464fa7af0dfcfefd8cd2f2650c5758d4
This commit is contained in:
commit
26c35b4dcf
|
@ -129,7 +129,6 @@ func addImportMap(s string) {
|
|||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: bundle [options] <src>\n")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -141,6 +140,7 @@ func main() {
|
|||
args := flag.Args()
|
||||
if len(args) != 1 {
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
if *dstPath != "" {
|
||||
|
|
|
@ -27,6 +27,18 @@ For GoSublime, follow the steps described here:
|
|||
|
||||
For other editors, you probably know what to do.
|
||||
|
||||
To exclude directories in your $GOPATH from being scanned for Go
|
||||
files, goimports respects a configuration file at
|
||||
$GOPATH/src/.goimportsignore which may contain blank lines, comment
|
||||
lines (beginning with '#'), or lines naming a directory relative to
|
||||
the configuration file to ignore when scanning. No globbing or regex
|
||||
patterns are allowed. Use the "-v" verbose flag to verify it's
|
||||
working and see what goimports is doing.
|
||||
|
||||
File bugs or feature requests at:
|
||||
|
||||
https://golang.org/issues/new?title=x/tools/cmd/goimports:+
|
||||
|
||||
Happy hacking!
|
||||
|
||||
*/
|
||||
|
|
|
@ -5,16 +5,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/scanner"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"runtime/trace"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/imports"
|
||||
|
@ -25,7 +30,13 @@ var (
|
|||
list = flag.Bool("l", false, "list files whose formatting differs from goimport's")
|
||||
write = flag.Bool("w", false, "write result to (source) file instead of stdout")
|
||||
doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
|
||||
srcdir = flag.String("srcdir", "", "choose imports as if source code is from `dir`")
|
||||
srcdir = flag.String("srcdir", "", "choose imports as if source code is from `dir`. When operating on a single file, dir may instead be the complete file name.")
|
||||
verbose = flag.Bool("v", false, "verbose logging")
|
||||
|
||||
cpuProfile = flag.String("cpuprofile", "", "CPU profile output")
|
||||
memProfile = flag.String("memprofile", "", "memory profile output")
|
||||
memProfileRate = flag.Int("memrate", 0, "if > 0, sets runtime.MemProfileRate")
|
||||
traceProfile = flag.String("trace", "", "trace profile output")
|
||||
|
||||
options = &imports.Options{
|
||||
TabWidth: 8,
|
||||
|
@ -38,6 +49,7 @@ var (
|
|||
|
||||
func init() {
|
||||
flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)")
|
||||
flag.StringVar(&imports.LocalPrefix, "local", "", "put imports beginning with this string after 3rd-party packages")
|
||||
}
|
||||
|
||||
func report(err error) {
|
||||
|
@ -57,9 +69,25 @@ func isGoFile(f os.FileInfo) bool {
|
|||
return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
|
||||
}
|
||||
|
||||
func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error {
|
||||
// argumentType is which mode goimports was invoked as.
|
||||
type argumentType int
|
||||
|
||||
const (
|
||||
// fromStdin means the user is piping their source into goimports.
|
||||
fromStdin argumentType = iota
|
||||
|
||||
// singleArg is the common case from editors, when goimports is run on
|
||||
// a single file.
|
||||
singleArg
|
||||
|
||||
// multipleArg is when the user ran "goimports file1.go file2.go"
|
||||
// or ran goimports on a directory tree.
|
||||
multipleArg
|
||||
)
|
||||
|
||||
func processFile(filename string, in io.Reader, out io.Writer, argType argumentType) error {
|
||||
opt := options
|
||||
if stdin {
|
||||
if argType == fromStdin {
|
||||
nopt := *options
|
||||
nopt.Fragment = true
|
||||
opt = &nopt
|
||||
|
@ -81,10 +109,31 @@ func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error
|
|||
|
||||
target := filename
|
||||
if *srcdir != "" {
|
||||
// Determine whether the provided -srcdirc is a directory or file
|
||||
// and then use it to override the target.
|
||||
//
|
||||
// See https://github.com/dominikh/go-mode.el/issues/146
|
||||
if isFile(*srcdir) {
|
||||
if argType == multipleArg {
|
||||
return errors.New("-srcdir value can't be a file when passing multiple arguments or when walking directories")
|
||||
}
|
||||
target = *srcdir
|
||||
} else if argType == singleArg && strings.HasSuffix(*srcdir, ".go") && !isDir(*srcdir) {
|
||||
// For a file which doesn't exist on disk yet, but might shortly.
|
||||
// e.g. user in editor opens $DIR/newfile.go and newfile.go doesn't yet exist on disk.
|
||||
// The goimports on-save hook writes the buffer to a temp file
|
||||
// first and runs goimports before the actual save to newfile.go.
|
||||
// The editor's buffer is named "newfile.go" so that is passed to goimports as:
|
||||
// goimports -srcdir=/gopath/src/pkg/newfile.go /tmp/gofmtXXXXXXXX.go
|
||||
// and then the editor reloads the result from the tmp file and writes
|
||||
// it to newfile.go.
|
||||
target = *srcdir
|
||||
} else {
|
||||
// Pretend that file is from *srcdir in order to decide
|
||||
// visible imports correctly.
|
||||
target = filepath.Join(*srcdir, filepath.Base(filename))
|
||||
}
|
||||
}
|
||||
|
||||
res, err := imports.Process(target, src, opt)
|
||||
if err != nil {
|
||||
|
@ -121,7 +170,7 @@ func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error
|
|||
|
||||
func visitFile(path string, f os.FileInfo, err error) error {
|
||||
if err == nil && isGoFile(f) {
|
||||
err = processFile(path, nil, os.Stdout, false)
|
||||
err = processFile(path, nil, os.Stdout, multipleArg)
|
||||
}
|
||||
if err != nil {
|
||||
report(err)
|
||||
|
@ -150,10 +199,54 @@ var parseFlags = func() []string {
|
|||
return flag.Args()
|
||||
}
|
||||
|
||||
func bufferedFileWriter(dest string) (w io.Writer, close func()) {
|
||||
f, err := os.Create(dest)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
bw := bufio.NewWriter(f)
|
||||
return bw, func() {
|
||||
if err := bw.Flush(); err != nil {
|
||||
log.Fatalf("error flushing %v: %v", dest, err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func gofmtMain() {
|
||||
flag.Usage = usage
|
||||
paths := parseFlags()
|
||||
|
||||
if *cpuProfile != "" {
|
||||
bw, flush := bufferedFileWriter(*cpuProfile)
|
||||
pprof.StartCPUProfile(bw)
|
||||
defer flush()
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
if *traceProfile != "" {
|
||||
bw, flush := bufferedFileWriter(*traceProfile)
|
||||
trace.Start(bw)
|
||||
defer flush()
|
||||
defer trace.Stop()
|
||||
}
|
||||
if *memProfileRate > 0 {
|
||||
runtime.MemProfileRate = *memProfileRate
|
||||
bw, flush := bufferedFileWriter(*memProfile)
|
||||
defer func() {
|
||||
runtime.GC() // materialize all statistics
|
||||
if err := pprof.WriteHeapProfile(bw); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
flush()
|
||||
}()
|
||||
}
|
||||
|
||||
if *verbose {
|
||||
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
||||
imports.Debug = true
|
||||
}
|
||||
if options.TabWidth < 0 {
|
||||
fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth)
|
||||
exitCode = 2
|
||||
|
@ -161,12 +254,17 @@ func gofmtMain() {
|
|||
}
|
||||
|
||||
if len(paths) == 0 {
|
||||
if err := processFile("<standard input>", os.Stdin, os.Stdout, true); err != nil {
|
||||
if err := processFile("<standard input>", os.Stdin, os.Stdout, fromStdin); err != nil {
|
||||
report(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
argType := singleArg
|
||||
if len(paths) > 1 {
|
||||
argType = multipleArg
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
switch dir, err := os.Stat(path); {
|
||||
case err != nil:
|
||||
|
@ -174,7 +272,7 @@ func gofmtMain() {
|
|||
case dir.IsDir():
|
||||
walkDir(path)
|
||||
default:
|
||||
if err := processFile(path, nil, os.Stdout, false); err != nil {
|
||||
if err := processFile(path, nil, os.Stdout, argType); err != nil {
|
||||
report(err)
|
||||
}
|
||||
}
|
||||
|
@ -207,3 +305,15 @@ func diff(b1, b2 []byte) (data []byte, err error) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
// isFile reports whether name is a file.
|
||||
func isFile(name string) bool {
|
||||
fi, err := os.Stat(name)
|
||||
return err == nil && fi.Mode().IsRegular()
|
||||
}
|
||||
|
||||
// isDir reports whether name is a directory.
|
||||
func isDir(name string) bool {
|
||||
fi, err := os.Stat(name)
|
||||
return err == nil && fi.IsDir()
|
||||
}
|
||||
|
|
|
@ -10,18 +10,43 @@
|
|||
|
||||
;;; Commentary:
|
||||
|
||||
;; To install the Go guru, run:
|
||||
;; To enable the Go guru in Emacs, use this command to download,
|
||||
;; build, and install the tool in $GOROOT/bin:
|
||||
;;
|
||||
;; $ go get golang.org/x/tools/cmd/guru
|
||||
;;
|
||||
;; Load this file into Emacs and set go-guru-scope to your
|
||||
;; configuration. Then, find a file of Go source code,
|
||||
;; select an expression of interest, and press `C-c C-o d' (for "describe")
|
||||
;; or run one of the other go-guru-xxx commands.
|
||||
;; Verify that the tool is on your $PATH:
|
||||
;;
|
||||
;; $ guru -help
|
||||
;; Go source code guru.
|
||||
;; Usage: guru [flags] <mode> <position>
|
||||
;; ...
|
||||
;;
|
||||
;; Then copy this file to a directory on your `load-path',
|
||||
;; and add this to your ~/.emacs:
|
||||
;;
|
||||
;; (require 'go-guru)
|
||||
;;
|
||||
;; Inside a buffer of Go source code, select an expression of
|
||||
;; interest, and type `C-c C-o d' (for "describe") or run one of the
|
||||
;; other go-guru-xxx commands. If you use `menu-bar-mode', these
|
||||
;; commands are available from the Guru menu.
|
||||
;;
|
||||
;; To enable identifier highlighting mode in a Go source buffer, use:
|
||||
;;
|
||||
;; (go-guru-hl-identifier-mode)
|
||||
;;
|
||||
;; To enable it automatically in all Go source buffers,
|
||||
;; add this to your ~/.emacs:
|
||||
;;
|
||||
;; (add-hook 'go-mode-hook #'go-guru-hl-identifier-mode)
|
||||
;;
|
||||
;; See http://golang.org/s/using-guru for more information about guru.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'compile)
|
||||
(require 'easymenu)
|
||||
(require 'go-mode)
|
||||
(require 'json)
|
||||
(require 'simple)
|
||||
|
@ -94,6 +119,25 @@
|
|||
|
||||
(define-key go-mode-map (kbd "C-c C-o") #'go-guru-map)
|
||||
|
||||
(easy-menu-define go-guru-mode-menu go-mode-map
|
||||
"Menu for Go Guru."
|
||||
'("Guru"
|
||||
["Jump to Definition" go-guru-definition t]
|
||||
["Show Referrers" go-guru-referrers t]
|
||||
["Show Free Names" go-guru-freevars t]
|
||||
["Describe Expression" go-guru-describe t]
|
||||
["Show Implements" go-guru-implements t]
|
||||
"---"
|
||||
["Show Callers" go-guru-callers t]
|
||||
["Show Callees" go-guru-callees t]
|
||||
["Show Callstack" go-guru-callstack t]
|
||||
"---"
|
||||
["Show Points-To" go-guru-pointsto t]
|
||||
["Show Which Errors" go-guru-whicherrs t]
|
||||
["Show Channel Peers" go-guru-peers t]
|
||||
"---"
|
||||
["Set pointer analysis scope..." go-guru-set-scope t]))
|
||||
|
||||
;;;###autoload
|
||||
(defun go-guru-set-scope ()
|
||||
"Set the scope for the Go guru, prompting the user to edit the previous scope.
|
||||
|
@ -209,7 +253,7 @@ variant of `compilation-mode'."
|
|||
(or buffer-file-name
|
||||
(error "Cannot use guru on a buffer without a file name"))
|
||||
(let* ((filename (file-truename buffer-file-name))
|
||||
(cmd (combine-and-quote-strings (go-guru--command mode filename)))
|
||||
(cmd (mapconcat #'shell-quote-argument (go-guru--command mode filename) " "))
|
||||
(process-connection-type nil) ; use pipe (not pty) so EOF closes stdin
|
||||
(procbuf (compilation-start cmd 'go-guru-output-mode)))
|
||||
(with-current-buffer procbuf
|
||||
|
|
|
@ -242,6 +242,12 @@ func TestGuru(t *testing.T) {
|
|||
"testdata/src/referrers-json/main.go",
|
||||
"testdata/src/what-json/main.go",
|
||||
} {
|
||||
if filename == "testdata/src/referrers/main.go" && runtime.GOOS == "plan9" {
|
||||
// Disable this test on plan9 since it expects a particular
|
||||
// wording for a "no such file or directory" error.
|
||||
continue
|
||||
}
|
||||
|
||||
json := strings.Contains(filename, "-json/")
|
||||
queries := parseQueries(t, filename)
|
||||
golden := filename + "lden"
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
|
||||
// guru: a tool for answering questions about Go source code.
|
||||
//
|
||||
// http://golang.org/s/oracle-design
|
||||
// http://golang.org/s/oracle-user-manual
|
||||
// http://golang.org/s/using-guru
|
||||
//
|
||||
// Run with -help flag or help subcommand for usage information.
|
||||
//
|
||||
|
@ -88,7 +87,7 @@ The -scope flag restricts analysis to the specified packages.
|
|||
encoding/...,-encoding/xml
|
||||
matches all encoding packages except encoding/xml.
|
||||
|
||||
User manual: http://golang.org/s/oracle-user-manual
|
||||
User manual: http://golang.org/s/using-guru
|
||||
|
||||
Example: describe syntax at offset 530 in this file (an import spec):
|
||||
|
||||
|
|
|
@ -113,6 +113,19 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"package": "what-json",
|
||||
"refs": [
|
||||
{
|
||||
"pos": "testdata/src/what-json/main.go:13:7",
|
||||
"text": "var _ lib.Var // @what pkg \"lib\""
|
||||
},
|
||||
{
|
||||
"pos": "testdata/src/what-json/main.go:14:8",
|
||||
"text": "type _ lib.T"
|
||||
}
|
||||
]
|
||||
}
|
||||
-------- @referrers ref-method --------
|
||||
{
|
||||
"objpos": "testdata/src/lib/lib.go:5:13",
|
||||
|
|
|
@ -28,6 +28,8 @@ references to package lib
|
|||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
||||
var v lib.Type = lib.Const // @referrers ref-package "lib"
|
||||
var x lib.T // @definition lexical-pkgname "lib"
|
||||
type _ lib.T
|
||||
var _ lib.Var // @what pkg "lib"
|
||||
|
||||
-------- @referrers ref-method --------
|
||||
references to func (Type).Method(x *int) *int
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package main
|
||||
|
||||
import "lib"
|
||||
|
||||
// Tests of 'what' queries, -format=json.
|
||||
// See go.tools/guru/guru_test.go for explanation.
|
||||
// See what-json.golden for expected query results.
|
||||
|
@ -7,3 +9,6 @@ package main
|
|||
func main() {
|
||||
f() // @what call "f"
|
||||
}
|
||||
|
||||
var _ lib.Var // @what pkg "lib"
|
||||
type _ lib.T
|
||||
|
|
|
@ -3,33 +3,33 @@
|
|||
"enclosing": [
|
||||
{
|
||||
"desc": "identifier",
|
||||
"start": 175,
|
||||
"end": 176
|
||||
"start": 189,
|
||||
"end": 190
|
||||
},
|
||||
{
|
||||
"desc": "function call",
|
||||
"start": 175,
|
||||
"end": 178
|
||||
"start": 189,
|
||||
"end": 192
|
||||
},
|
||||
{
|
||||
"desc": "expression statement",
|
||||
"start": 175,
|
||||
"end": 178
|
||||
"start": 189,
|
||||
"end": 192
|
||||
},
|
||||
{
|
||||
"desc": "block",
|
||||
"start": 172,
|
||||
"end": 198
|
||||
"start": 186,
|
||||
"end": 212
|
||||
},
|
||||
{
|
||||
"desc": "function declaration",
|
||||
"start": 160,
|
||||
"end": 198
|
||||
"start": 174,
|
||||
"end": 212
|
||||
},
|
||||
{
|
||||
"desc": "source file",
|
||||
"start": 0,
|
||||
"end": 198
|
||||
"end": 259
|
||||
}
|
||||
],
|
||||
"modes": [
|
||||
|
@ -46,3 +46,48 @@
|
|||
"srcdir": "testdata/src",
|
||||
"importpath": "what-json"
|
||||
}
|
||||
-------- @what pkg --------
|
||||
{
|
||||
"enclosing": [
|
||||
{
|
||||
"desc": "identifier",
|
||||
"start": 220,
|
||||
"end": 223
|
||||
},
|
||||
{
|
||||
"desc": "selector",
|
||||
"start": 220,
|
||||
"end": 227
|
||||
},
|
||||
{
|
||||
"desc": "value specification",
|
||||
"start": 218,
|
||||
"end": 227
|
||||
},
|
||||
{
|
||||
"desc": "variable declaration",
|
||||
"start": 214,
|
||||
"end": 227
|
||||
},
|
||||
{
|
||||
"desc": "source file",
|
||||
"start": 0,
|
||||
"end": 259
|
||||
}
|
||||
],
|
||||
"modes": [
|
||||
"definition",
|
||||
"describe",
|
||||
"freevars",
|
||||
"implements",
|
||||
"pointsto",
|
||||
"referrers"
|
||||
],
|
||||
"srcdir": "testdata/src",
|
||||
"importpath": "what-json",
|
||||
"object": "lib",
|
||||
"sameids": [
|
||||
"$GOPATH/src/what-json/main.go:13:7",
|
||||
"$GOPATH/src/what-json/main.go:14:8"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -117,7 +117,26 @@ func what(q *Query) error {
|
|||
// it uses the best-effort name resolution done by go/parser.
|
||||
var sameids []token.Pos
|
||||
var object string
|
||||
if id, ok := qpos.path[0].(*ast.Ident); ok && id.Obj != nil {
|
||||
if id, ok := qpos.path[0].(*ast.Ident); ok {
|
||||
if id.Obj == nil {
|
||||
// An unresolved identifier is potentially a package name.
|
||||
// Resolve them with a simple importer (adds ~100µs).
|
||||
importer := func(imports map[string]*ast.Object, path string) (*ast.Object, error) {
|
||||
pkg, ok := imports[path]
|
||||
if !ok {
|
||||
pkg = &ast.Object{
|
||||
Kind: ast.Pkg,
|
||||
Name: filepath.Base(path), // a guess
|
||||
}
|
||||
imports[path] = pkg
|
||||
}
|
||||
return pkg, nil
|
||||
}
|
||||
f := qpos.path[len(qpos.path)-1].(*ast.File)
|
||||
ast.NewPackage(qpos.fset, map[string]*ast.File{"": f}, importer, nil)
|
||||
}
|
||||
|
||||
if id.Obj != nil {
|
||||
object = id.Obj.Name
|
||||
decl := qpos.path[len(qpos.path)-1]
|
||||
ast.Inspect(decl, func(n ast.Node) bool {
|
||||
|
@ -127,6 +146,7 @@ func what(q *Query) error {
|
|||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
q.Output(qpos.fset, &whatResult{
|
||||
path: qpos.path,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
BasedOnStyle: Google
|
|
@ -0,0 +1 @@
|
|||
/node_modules/
|
|
@ -0,0 +1,45 @@
|
|||
# Go Heap Viewer Client
|
||||
|
||||
This directory contains the client Typescript code for the Go
|
||||
heap viewer.
|
||||
|
||||
## Typescript Tooling
|
||||
|
||||
Below are instructions for downloading tooling and files to
|
||||
help make the development process more convenient. These tools
|
||||
are not required for contributing or running the heap viewer-
|
||||
they are just meant as development aids.
|
||||
|
||||
## Node and NPM
|
||||
|
||||
We use npm to manage the dependencies for these tools. There are
|
||||
a couple of ways of installing npm on your system, but we recommend
|
||||
using nvm.
|
||||
|
||||
Run the following command to install nvm:
|
||||
|
||||
[shell]$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.3/install.sh | bash
|
||||
|
||||
or see the instructions on [the nvm github page](github.com/creationix/nvm)
|
||||
for alternative methods. This will put the nvm tool in your home directory
|
||||
and edit your path to add nvm, node and other tools you install using them.
|
||||
Once nvm is installed, use
|
||||
|
||||
[shell]$ nvm install node
|
||||
|
||||
then
|
||||
|
||||
[shell]$ nvm use node
|
||||
|
||||
to install node.js.
|
||||
|
||||
Once node is installed, you can install typescript using
|
||||
|
||||
[shell]$ npm install -g typescript
|
||||
|
||||
Finally, import type definitions into this project by running
|
||||
|
||||
[shell]$ npm install
|
||||
|
||||
in this directory. They will be imported into the node_packages directory
|
||||
and be automatically available to the Typescript compiler.
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* A hamburger menu element.
|
||||
*/
|
||||
class HamburgerElement extends HTMLElement {
|
||||
attachedCallback() {
|
||||
this.innerHTML = '☰'; // Unicode character for hamburger menu.
|
||||
}
|
||||
}
|
||||
document.registerElement('heap-hamburger', HamburgerElement);
|
||||
|
||||
/**
|
||||
* A heading for the page with a hamburger menu and a title.
|
||||
*/
|
||||
export class HeadingElement extends HTMLElement {
|
||||
attachedCallback() {
|
||||
this.style.display = 'block';
|
||||
this.style.backgroundColor = '#2196F3';
|
||||
this.style.webkitUserSelect = 'none';
|
||||
this.style.cursor = 'default';
|
||||
this.style.color = '#FFFFFF';
|
||||
this.style.padding = '10px';
|
||||
this.innerHTML = `
|
||||
<div style="margin:0px; font-size:2em"><heap-hamburger></heap-hamburger> Go Heap Viewer</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
document.registerElement('heap-heading', HeadingElement);
|
||||
|
||||
/**
|
||||
* Reset body's margin and padding, and set font.
|
||||
*/
|
||||
function clearStyle() {
|
||||
document.head.innerHTML += `
|
||||
<style>
|
||||
* {font-family: Roboto,Helvetica}
|
||||
body {margin: 0px; padding:0px}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
export function main() {
|
||||
document.title = 'Go Heap Viewer';
|
||||
clearStyle();
|
||||
document.body.appendChild(document.createElement("heap-heading"));
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
import {main} from './main';
|
||||
|
||||
describe('main', () => {
|
||||
it('sets the document\'s title', () => {
|
||||
main();
|
||||
expect(document.title).toBe('Go Heap Viewer');
|
||||
});
|
||||
|
||||
it('has a heading', () => {
|
||||
main();
|
||||
expect(document.querySelector('heap-heading')).toBeDefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"//": [
|
||||
"Copyright 2016 The Go Authors. All rights reserved.",
|
||||
"Use of this source code is governed by a BSD-style",
|
||||
"license that can be found in the LICENSE file.",
|
||||
|
||||
"This file exists to help import typescript typings",
|
||||
"for web features used in this project. Neither the",
|
||||
"typings nor node or npm are required to do development",
|
||||
"on the code in this project.",
|
||||
|
||||
"If you do have npm installed, use the `npm i` command",
|
||||
"in this directory to install the typings."
|
||||
],
|
||||
"private": true,
|
||||
"name": "@golangtools/heapview",
|
||||
"version": "0.0.0",
|
||||
"devDependencies": {
|
||||
"@types/webcomponents.js": "latest",
|
||||
"@types/whatwg-fetch": "latest",
|
||||
"@types/jasmine": "latest",
|
||||
|
||||
"jasmine-core": "latest",
|
||||
"karma": "latest",
|
||||
"karma-jasmine": "latest",
|
||||
"karma-chrome-launcher": "latest",
|
||||
|
||||
"clang-format": "latest"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "karma start testing/karma.conf.js",
|
||||
"format": "find . | grep '\\(test_main\\.js\\|\\.ts\\)$' | xargs clang-format -i",
|
||||
"lint": "tslint --project ."
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
module.exports = config => {
|
||||
config.set({
|
||||
frameworks: ['jasmine'],
|
||||
basePath: '../../../..',
|
||||
files: [
|
||||
'third_party/webcomponents/customelements.js',
|
||||
'third_party/typescript/typescript.js',
|
||||
'third_party/moduleloader/moduleloader.js',
|
||||
'cmd/heapview/client/testing/test_main.js',
|
||||
{pattern: 'cmd/heapview/client/**/*.ts', included: false},
|
||||
],
|
||||
browsers: ['Chrome'],
|
||||
plugins: [
|
||||
'karma-jasmine',
|
||||
'karma-chrome-launcher'
|
||||
],
|
||||
})
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Configure module loader.
|
||||
System.transpiler = 'typescript'
|
||||
System.typescriptOptions = {
|
||||
target: ts.ScriptTarget.ES2015
|
||||
};
|
||||
System.locate = (load) => load.name + '.ts';
|
||||
|
||||
// Determine set of test files.
|
||||
var tests = [];
|
||||
for (var file in window.__karma__.files) {
|
||||
if (window.__karma__.files.hasOwnProperty(file)) {
|
||||
if (/_test\.ts$/.test(file)) {
|
||||
tests.push(file.slice(0, -3));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Steal loaded callback so we can block until we're
|
||||
// done loading all test modules.
|
||||
var loadedCallback = window.__karma__.loaded.bind(window.__karma__);
|
||||
window.__karma__.loaded = () => {};
|
||||
|
||||
// Load all test modules, and then call loadedCallback.
|
||||
var promises = [];
|
||||
for (var i = 0; i < tests.length; i++) {
|
||||
promises.push(System.import(tests[i]));
|
||||
}
|
||||
Promise.all(promises).then(loadedCallback);
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains configuration for the Typescript
|
||||
// compiler if you're running it locally for typechecking
|
||||
// and other tooling. The Typescript compiler is
|
||||
// not necessary to do development on this project.
|
||||
|
||||
{
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This tslint file is based on a configuration used at
|
||||
// Google.
|
||||
|
||||
{
|
||||
"rules": {
|
||||
"class-name": true,
|
||||
"forin": true,
|
||||
"interface-name": [true, "never-prefix"],
|
||||
"jsdoc-format": true,
|
||||
"label-position": true,
|
||||
"label-undefined": true,
|
||||
"new-parens": true,
|
||||
"no-angle-bracket-type-assertion": true,
|
||||
"no-construct": true,
|
||||
"no-debugger": true,
|
||||
"no-namespace": [true, "allow-declarations"],
|
||||
"no-reference": true,
|
||||
"no-require-imports": true,
|
||||
"no-unused-expression": true,
|
||||
"no-unused-variable": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-keyword": true,
|
||||
"semicolon": [true, "always"],
|
||||
"switch-default": true,
|
||||
"triple-equals": [true, "allow-null-check"],
|
||||
"use-isnan": true,
|
||||
"variable-name": [
|
||||
true,
|
||||
"check-format",
|
||||
"ban-keywords",
|
||||
"allow-leading-underscore",
|
||||
"allow-trailing-underscore",
|
||||
"allow-pascal-case"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// heapview is a tool for viewing Go heap dumps.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 8080, "service port")
|
||||
|
||||
var index = `<!DOCTYPE html>
|
||||
<script src="js/customelements.js"></script>
|
||||
<script src="js/typescript.js"></script>
|
||||
<script src="js/moduleloader.js"></script>
|
||||
<script>
|
||||
System.transpiler = 'typescript';
|
||||
System.typescriptOptions = {target: ts.ScriptTarget.ES2015};
|
||||
System.locate = (load) => load.name + '.ts';
|
||||
</script>
|
||||
<script type="module">
|
||||
import {main} from './client/main';
|
||||
main();
|
||||
</script>
|
||||
`
|
||||
|
||||
func toolsDir() string {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
if gopath == "" {
|
||||
log.Println("error: GOPATH not set. Can't find client files")
|
||||
os.Exit(1)
|
||||
}
|
||||
return filepath.Join(filepath.SplitList(gopath)[0], "/src/golang.org/x/tools")
|
||||
}
|
||||
|
||||
var parseFlags = func() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
var addHandlers = func() {
|
||||
// Directly serve typescript code in client directory for development.
|
||||
http.Handle("/client/", http.StripPrefix("/client",
|
||||
http.FileServer(http.Dir(filepath.Join(toolsDir(), "cmd/heapview/client")))))
|
||||
|
||||
// Serve typescript.js and moduleloader.js for development.
|
||||
http.HandleFunc("/js/typescript.js", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, filepath.Join(toolsDir(), "third_party/typescript/typescript.js"))
|
||||
})
|
||||
http.HandleFunc("/js/moduleloader.js", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, filepath.Join(toolsDir(), "third_party/moduleloader/moduleloader.js"))
|
||||
})
|
||||
http.HandleFunc("/js/customelements.js", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, filepath.Join(toolsDir(), "third_party/webcomponents/customelements.js"))
|
||||
})
|
||||
|
||||
// Serve index.html using html string above.
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
io.WriteString(w, index)
|
||||
})
|
||||
}
|
||||
|
||||
var listenAndServe = func() {
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
|
||||
}
|
||||
|
||||
func main() {
|
||||
parseFlags()
|
||||
addHandlers()
|
||||
listenAndServe()
|
||||
}
|
|
@ -6,6 +6,10 @@
|
|||
// http://golang.org/s/oracle-design
|
||||
// http://golang.org/s/oracle-user-manual
|
||||
//
|
||||
// DEPRECATED: oracle has been superseded by guru;
|
||||
// see https://golang.org/s/using-guru for details.
|
||||
// This package will be deleted on October 1, 2016.
|
||||
//
|
||||
// Run with -help flag or help subcommand for usage information.
|
||||
//
|
||||
package main // import "golang.org/x/tools/cmd/oracle"
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
1. Deploy the app.
|
||||
|
||||
To deploy tip.golang.org:
|
||||
$ gcloud --project golang-org preview app deploy --no-promote godoc.yaml
|
||||
$ gcloud --project golang-org app deploy --no-promote godoc.yaml
|
||||
|
||||
To deploy talks.golang.org:
|
||||
$ gcloud --project golang-org preview app deploy --no-promote talks.yaml
|
||||
$ gcloud --project golang-org app deploy --no-promote talks.yaml
|
||||
|
||||
2. Wait until the deployed version is serving requests.
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Command tipgodoc is the beginning of the new tip.golang.org server,
|
||||
// Command tip is the tip.golang.org server,
|
||||
// serving the latest HEAD straight from the Git oven.
|
||||
package main
|
||||
|
||||
|
|
|
@ -181,7 +181,7 @@ func (p *importer) declare(obj types.Object) {
|
|||
// imported.
|
||||
// (See also the comment in cmd/compile/internal/gc/bimport.go importer.obj,
|
||||
// switch case importing functions).
|
||||
panic(fmt.Sprintf("%s already declared", alt.Name()))
|
||||
panic(fmt.Sprintf("inconsistent import:\n\t%v\npreviously imported as:\n\t%v\n", alt, obj))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -175,7 +175,7 @@ func Import(packages map[string]*types.Package, path, srcDir string) (pkg *types
|
|||
data, err = ioutil.ReadAll(buf)
|
||||
if err == nil {
|
||||
fset := token.NewFileSet()
|
||||
_, pkg, err = BImportData(fset, packages, data, path)
|
||||
_, pkg, err = BImportData(fset, packages, data, id)
|
||||
return
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -363,3 +363,67 @@ func TestIssue13898(t *testing.T) {
|
|||
t.Fatalf("found %v; want go/types", m.Pkg())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue15517(t *testing.T) {
|
||||
skipSpecialPlatforms(t)
|
||||
|
||||
// This package only handles gc export data.
|
||||
if runtime.Compiler != "gc" {
|
||||
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
|
||||
return
|
||||
}
|
||||
|
||||
// On windows, we have to set the -D option for the compiler to avoid having a drive
|
||||
// letter and an illegal ':' in the import path - just skip it (see also issue #3483).
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("avoid dealing with relative paths/drive letters on windows")
|
||||
}
|
||||
|
||||
if f := compile(t, "testdata", "p.go"); f != "" {
|
||||
defer os.Remove(f)
|
||||
}
|
||||
|
||||
// Multiple imports of p must succeed without redeclaration errors.
|
||||
// We use an import path that's not cleaned up so that the eventual
|
||||
// file path for the package is different from the package path; this
|
||||
// will expose the error if it is present.
|
||||
//
|
||||
// (Issue: Both the textual and the binary importer used the file path
|
||||
// of the package to be imported as key into the shared packages map.
|
||||
// However, the binary importer then used the package path to identify
|
||||
// the imported package to mark it as complete; effectively marking the
|
||||
// wrong package as complete. By using an "unclean" package path, the
|
||||
// file and package path are different, exposing the problem if present.
|
||||
// The same issue occurs with vendoring.)
|
||||
imports := make(map[string]*types.Package)
|
||||
for i := 0; i < 3; i++ {
|
||||
if _, err := Import(imports, "./././testdata/p", "."); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue15920(t *testing.T) {
|
||||
skipSpecialPlatforms(t)
|
||||
|
||||
// This package only handles gc export data.
|
||||
if runtime.Compiler != "gc" {
|
||||
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
|
||||
return
|
||||
}
|
||||
|
||||
// On windows, we have to set the -D option for the compiler to avoid having a drive
|
||||
// letter and an illegal ':' in the import path - just skip it (see also issue #3483).
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("avoid dealing with relative paths/drive letters on windows")
|
||||
}
|
||||
|
||||
if f := compile(t, "testdata", "issue15920.go"); f != "" {
|
||||
defer os.Remove(f)
|
||||
}
|
||||
|
||||
imports := make(map[string]*types.Package)
|
||||
if _, err := Import(imports, "./testdata/issue15920", "."); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package p
|
||||
|
||||
// The underlying type of Error is the underlying type of error.
|
||||
// Make sure we can import this again without problems.
|
||||
type Error error
|
||||
|
||||
func F() Error { return nil }
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Input for TestIssue15517
|
||||
|
||||
package p
|
||||
|
||||
const C = 0
|
||||
|
||||
var V int
|
||||
|
||||
func F() {}
|
|
@ -2227,13 +2227,13 @@ func (b *builder) buildFuncDecl(pkg *Package, decl *ast.FuncDecl) {
|
|||
b.buildFunction(fn)
|
||||
}
|
||||
|
||||
// BuildAll calls Package.Build() for each package in prog.
|
||||
// Build calls Package.Build for each package in prog.
|
||||
// Building occurs in parallel unless the BuildSerially mode flag was set.
|
||||
//
|
||||
// BuildAll is intended for whole-program analysis; a typical compiler
|
||||
// Build is intended for whole-program analysis; a typical compiler
|
||||
// need only build a single package.
|
||||
//
|
||||
// BuildAll is idempotent and thread-safe.
|
||||
// Build is idempotent and thread-safe.
|
||||
//
|
||||
func (prog *Program) Build() {
|
||||
var wg sync.WaitGroup
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
// loaded from source. An SSA package is created for each transitively
|
||||
// error-free package of lprog.
|
||||
//
|
||||
// Code for bodies of functions is not built until BuildAll() is called
|
||||
// Code for bodies of functions is not built until Build is called
|
||||
// on the result.
|
||||
//
|
||||
// mode controls diagnostics and checking during SSA construction.
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// A faster implementation of filepath.Walk.
|
||||
//
|
||||
// filepath.Walk's design necessarily calls os.Lstat on each file,
|
||||
// even if the caller needs less info. And goimports only need to know
|
||||
// the type of each file. The kernel interface provides the type in
|
||||
// the Readdir call but the standard library ignored it.
|
||||
// fastwalk_unix.go contains a fork of the syscall routines.
|
||||
//
|
||||
// See golang.org/issue/16399
|
||||
|
||||
package imports
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// traverseLink is a sentinel error for fastWalk, similar to filepath.SkipDir.
|
||||
var traverseLink = errors.New("traverse symlink, assuming target is a directory")
|
||||
|
||||
// fastWalk walks the file tree rooted at root, calling walkFn for
|
||||
// each file or directory in the tree, including root.
|
||||
//
|
||||
// If fastWalk returns filepath.SkipDir, the directory is skipped.
|
||||
//
|
||||
// Unlike filepath.Walk:
|
||||
// * file stat calls must be done by the user.
|
||||
// The only provided metadata is the file type, which does not include
|
||||
// any permission bits.
|
||||
// * multiple goroutines stat the filesystem concurrently. The provided
|
||||
// walkFn must be safe for concurrent use.
|
||||
// * fastWalk can follow symlinks if walkFn returns the traverseLink
|
||||
// sentinel error. It is the walkFn's responsibility to prevent
|
||||
// fastWalk from going into symlink cycles.
|
||||
func fastWalk(root string, walkFn func(path string, typ os.FileMode) error) error {
|
||||
// TODO(bradfitz): make numWorkers configurable? We used a
|
||||
// minimum of 4 to give the kernel more info about multiple
|
||||
// things we want, in hopes its I/O scheduling can take
|
||||
// advantage of that. Hopefully most are in cache. Maybe 4 is
|
||||
// even too low of a minimum. Profile more.
|
||||
numWorkers := 4
|
||||
if n := runtime.NumCPU(); n > numWorkers {
|
||||
numWorkers = n
|
||||
}
|
||||
w := &walker{
|
||||
fn: walkFn,
|
||||
enqueuec: make(chan walkItem, numWorkers), // buffered for performance
|
||||
workc: make(chan walkItem, numWorkers), // buffered for performance
|
||||
donec: make(chan struct{}),
|
||||
|
||||
// buffered for correctness & not leaking goroutines:
|
||||
resc: make(chan error, numWorkers),
|
||||
}
|
||||
defer close(w.donec)
|
||||
// TODO(bradfitz): start the workers as needed? maybe not worth it.
|
||||
for i := 0; i < numWorkers; i++ {
|
||||
go w.doWork()
|
||||
}
|
||||
todo := []walkItem{{dir: root}}
|
||||
out := 0
|
||||
for {
|
||||
workc := w.workc
|
||||
var workItem walkItem
|
||||
if len(todo) == 0 {
|
||||
workc = nil
|
||||
} else {
|
||||
workItem = todo[len(todo)-1]
|
||||
}
|
||||
select {
|
||||
case workc <- workItem:
|
||||
todo = todo[:len(todo)-1]
|
||||
out++
|
||||
case it := <-w.enqueuec:
|
||||
todo = append(todo, it)
|
||||
case err := <-w.resc:
|
||||
out--
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if out == 0 && len(todo) == 0 {
|
||||
// It's safe to quit here, as long as the buffered
|
||||
// enqueue channel isn't also readable, which might
|
||||
// happen if the worker sends both another unit of
|
||||
// work and its result before the other select was
|
||||
// scheduled and both w.resc and w.enqueuec were
|
||||
// readable.
|
||||
select {
|
||||
case it := <-w.enqueuec:
|
||||
todo = append(todo, it)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// doWork reads directories as instructed (via workc) and runs the
|
||||
// user's callback function.
|
||||
func (w *walker) doWork() {
|
||||
for {
|
||||
select {
|
||||
case <-w.donec:
|
||||
return
|
||||
case it := <-w.workc:
|
||||
w.resc <- w.walk(it.dir, !it.callbackDone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type walker struct {
|
||||
fn func(path string, typ os.FileMode) error
|
||||
|
||||
donec chan struct{} // closed on fastWalk's return
|
||||
workc chan walkItem // to workers
|
||||
enqueuec chan walkItem // from workers
|
||||
resc chan error // from workers
|
||||
}
|
||||
|
||||
type walkItem struct {
|
||||
dir string
|
||||
callbackDone bool // callback already called; don't do it again
|
||||
}
|
||||
|
||||
func (w *walker) enqueue(it walkItem) {
|
||||
select {
|
||||
case w.enqueuec <- it:
|
||||
case <-w.donec:
|
||||
}
|
||||
}
|
||||
|
||||
func (w *walker) onDirEnt(dirName, baseName string, typ os.FileMode) error {
|
||||
joined := dirName + string(os.PathSeparator) + baseName
|
||||
if typ == os.ModeDir {
|
||||
w.enqueue(walkItem{dir: joined})
|
||||
return nil
|
||||
}
|
||||
|
||||
err := w.fn(joined, typ)
|
||||
if typ == os.ModeSymlink {
|
||||
if err == traverseLink {
|
||||
// Set callbackDone so we don't call it twice for both the
|
||||
// symlink-as-symlink and the symlink-as-directory later:
|
||||
w.enqueue(walkItem{dir: joined, callbackDone: true})
|
||||
return nil
|
||||
}
|
||||
if err == filepath.SkipDir {
|
||||
// Permit SkipDir on symlinks too.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
func (w *walker) walk(root string, runUserCallback bool) error {
|
||||
if runUserCallback {
|
||||
err := w.fn(root, os.ModeDir)
|
||||
if err == filepath.SkipDir {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return readDir(root, w.onDirEnt)
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build freebsd openbsd netbsd
|
||||
|
||||
package imports
|
||||
|
||||
import "syscall"
|
||||
|
||||
func direntInode(dirent *syscall.Dirent) uint64 {
|
||||
return uint64(dirent.Fileno)
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux darwin
|
||||
|
||||
package imports
|
||||
|
||||
import "syscall"
|
||||
|
||||
func direntInode(dirent *syscall.Dirent) uint64 {
|
||||
return uint64(dirent.Ino)
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !linux,!darwin,!freebsd,!openbsd,!netbsd
|
||||
|
||||
package imports
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
// readDir calls fn for each directory entry in dirName.
|
||||
// It does not descend into directories or follow symlinks.
|
||||
// If fn returns a non-nil error, readDir returns with that error
|
||||
// immediately.
|
||||
func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error {
|
||||
fis, err := ioutil.ReadDir(dirName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fi := range fis {
|
||||
if err := fn(dirName, fi.Name(), fi.Mode()&os.ModeType); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package imports
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func formatFileModes(m map[string]os.FileMode) string {
|
||||
var keys []string
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
var buf bytes.Buffer
|
||||
for _, k := range keys {
|
||||
fmt.Fprintf(&buf, "%-20s: %v\n", k, m[k])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func testFastWalk(t *testing.T, files map[string]string, callback func(path string, typ os.FileMode) error, want map[string]os.FileMode) {
|
||||
testConfig{
|
||||
gopathFiles: files,
|
||||
}.test(t, func(t *goimportTest) {
|
||||
got := map[string]os.FileMode{}
|
||||
var mu sync.Mutex
|
||||
if err := fastWalk(t.gopath, func(path string, typ os.FileMode) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if !strings.HasPrefix(path, t.gopath) {
|
||||
t.Fatal("bogus prefix on %q, expect %q", path, t.gopath)
|
||||
}
|
||||
key := filepath.ToSlash(strings.TrimPrefix(path, t.gopath))
|
||||
if old, dup := got[key]; dup {
|
||||
t.Fatalf("callback called twice for key %q: %v -> %v", key, old, typ)
|
||||
}
|
||||
got[key] = typ
|
||||
return callback(path, typ)
|
||||
}); err != nil {
|
||||
t.Fatalf("callback returned: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("walk mismatch.\n got:\n%v\nwant:\n%v", formatFileModes(got), formatFileModes(want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFastWalk_Basic(t *testing.T) {
|
||||
testFastWalk(t, map[string]string{
|
||||
"foo/foo.go": "one",
|
||||
"bar/bar.go": "two",
|
||||
"skip/skip.go": "skip",
|
||||
},
|
||||
func(path string, typ os.FileMode) error {
|
||||
return nil
|
||||
},
|
||||
map[string]os.FileMode{
|
||||
"": os.ModeDir,
|
||||
"/src": os.ModeDir,
|
||||
"/src/bar": os.ModeDir,
|
||||
"/src/bar/bar.go": 0,
|
||||
"/src/foo": os.ModeDir,
|
||||
"/src/foo/foo.go": 0,
|
||||
"/src/skip": os.ModeDir,
|
||||
"/src/skip/skip.go": 0,
|
||||
})
|
||||
}
|
||||
|
||||
func TestFastWalk_Symlink(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "windows", "plan9":
|
||||
t.Skipf("skipping on %s", runtime.GOOS)
|
||||
}
|
||||
testFastWalk(t, map[string]string{
|
||||
"foo/foo.go": "one",
|
||||
"bar/bar.go": "LINK:../foo.go",
|
||||
"symdir": "LINK:foo",
|
||||
},
|
||||
func(path string, typ os.FileMode) error {
|
||||
return nil
|
||||
},
|
||||
map[string]os.FileMode{
|
||||
"": os.ModeDir,
|
||||
"/src": os.ModeDir,
|
||||
"/src/bar": os.ModeDir,
|
||||
"/src/bar/bar.go": os.ModeSymlink,
|
||||
"/src/foo": os.ModeDir,
|
||||
"/src/foo/foo.go": 0,
|
||||
"/src/symdir": os.ModeSymlink,
|
||||
})
|
||||
}
|
||||
|
||||
func TestFastWalk_SkipDir(t *testing.T) {
|
||||
testFastWalk(t, map[string]string{
|
||||
"foo/foo.go": "one",
|
||||
"bar/bar.go": "two",
|
||||
"skip/skip.go": "skip",
|
||||
},
|
||||
func(path string, typ os.FileMode) error {
|
||||
if typ == os.ModeDir && strings.HasSuffix(path, "skip") {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
},
|
||||
map[string]os.FileMode{
|
||||
"": os.ModeDir,
|
||||
"/src": os.ModeDir,
|
||||
"/src/bar": os.ModeDir,
|
||||
"/src/bar/bar.go": 0,
|
||||
"/src/foo": os.ModeDir,
|
||||
"/src/foo/foo.go": 0,
|
||||
"/src/skip": os.ModeDir,
|
||||
})
|
||||
}
|
||||
|
||||
func TestFastWalk_TraverseSymlink(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "windows", "plan9":
|
||||
t.Skipf("skipping on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
testFastWalk(t, map[string]string{
|
||||
"foo/foo.go": "one",
|
||||
"bar/bar.go": "two",
|
||||
"skip/skip.go": "skip",
|
||||
"symdir": "LINK:foo",
|
||||
},
|
||||
func(path string, typ os.FileMode) error {
|
||||
if typ == os.ModeSymlink {
|
||||
return traverseLink
|
||||
}
|
||||
return nil
|
||||
},
|
||||
map[string]os.FileMode{
|
||||
"": os.ModeDir,
|
||||
"/src": os.ModeDir,
|
||||
"/src/bar": os.ModeDir,
|
||||
"/src/bar/bar.go": 0,
|
||||
"/src/foo": os.ModeDir,
|
||||
"/src/foo/foo.go": 0,
|
||||
"/src/skip": os.ModeDir,
|
||||
"/src/skip/skip.go": 0,
|
||||
"/src/symdir": os.ModeSymlink,
|
||||
"/src/symdir/foo.go": 0,
|
||||
})
|
||||
}
|
||||
|
||||
var benchDir = flag.String("benchdir", runtime.GOROOT(), "The directory to scan for BenchmarkFastWalk")
|
||||
|
||||
func BenchmarkFastWalk(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := fastWalk(*benchDir, func(path string, typ os.FileMode) error { return nil })
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux darwin freebsd openbsd netbsd
|
||||
|
||||
package imports
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const blockSize = 8 << 10
|
||||
|
||||
// unknownFileMode is a sentinel (and bogus) os.FileMode
|
||||
// value used to represent a syscall.DT_UNKNOWN Dirent.Type.
|
||||
const unknownFileMode os.FileMode = os.ModeNamedPipe | os.ModeSocket | os.ModeDevice
|
||||
|
||||
func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error {
|
||||
fd, err := syscall.Open(dirName, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
// The buffer must be at least a block long.
|
||||
buf := make([]byte, blockSize) // stack-allocated; doesn't escape
|
||||
bufp := 0 // starting read position in buf
|
||||
nbuf := 0 // end valid data in buf
|
||||
for {
|
||||
if bufp >= nbuf {
|
||||
bufp = 0
|
||||
nbuf, err = syscall.ReadDirent(fd, buf)
|
||||
if err != nil {
|
||||
return os.NewSyscallError("readdirent", err)
|
||||
}
|
||||
if nbuf <= 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
consumed, name, typ := parseDirEnt(buf[bufp:nbuf])
|
||||
bufp += consumed
|
||||
if name == "" || name == "." || name == ".." {
|
||||
continue
|
||||
}
|
||||
// Fallback for filesystems (like old XFS) that don't
|
||||
// support Dirent.Type and have DT_UNKNOWN (0) there
|
||||
// instead.
|
||||
if typ == unknownFileMode {
|
||||
fi, err := os.Lstat(dirName + "/" + name)
|
||||
if err != nil {
|
||||
// It got deleted in the meantime.
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
typ = fi.Mode() & os.ModeType
|
||||
}
|
||||
if err := fn(dirName, name, typ); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseDirEnt(buf []byte) (consumed int, name string, typ os.FileMode) {
|
||||
// golang.org/issue/15653
|
||||
dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[0]))
|
||||
if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v {
|
||||
panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d", len(buf), v))
|
||||
}
|
||||
if len(buf) < int(dirent.Reclen) {
|
||||
panic(fmt.Sprintf("buf size %d < record length %d", len(buf), dirent.Reclen))
|
||||
}
|
||||
consumed = int(dirent.Reclen)
|
||||
if direntInode(dirent) == 0 { // File absent in directory.
|
||||
return
|
||||
}
|
||||
switch dirent.Type {
|
||||
case syscall.DT_REG:
|
||||
typ = 0
|
||||
case syscall.DT_DIR:
|
||||
typ = os.ModeDir
|
||||
case syscall.DT_LNK:
|
||||
typ = os.ModeSymlink
|
||||
case syscall.DT_BLK:
|
||||
typ = os.ModeDevice
|
||||
case syscall.DT_FIFO:
|
||||
typ = os.ModeNamedPipe
|
||||
case syscall.DT_SOCK:
|
||||
typ = os.ModeSocket
|
||||
case syscall.DT_UNKNOWN:
|
||||
typ = unknownFileMode
|
||||
default:
|
||||
// Skip weird things.
|
||||
// It's probably a DT_WHT (http://lwn.net/Articles/325369/)
|
||||
// or something. Revisit if/when this package is moved outside
|
||||
// of goimports. goimports only cares about regular files,
|
||||
// symlinks, and directories.
|
||||
return
|
||||
}
|
||||
|
||||
nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0]))
|
||||
nameLen := bytes.IndexByte(nameBuf[:], 0)
|
||||
if nameLen < 0 {
|
||||
panic("failed to find terminating 0 byte in dirent")
|
||||
}
|
||||
|
||||
// Special cases for common things:
|
||||
if nameLen == 1 && nameBuf[0] == '.' {
|
||||
name = "."
|
||||
} else if nameLen == 2 && nameBuf[0] == '.' && nameBuf[1] == '.' {
|
||||
name = ".."
|
||||
} else {
|
||||
name = string(nameBuf[:nameLen])
|
||||
}
|
||||
return
|
||||
}
|
747
imports/fix.go
747
imports/fix.go
|
@ -5,23 +5,46 @@
|
|||
package imports
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
// Debug controls verbose logging.
|
||||
var Debug = false
|
||||
|
||||
var (
|
||||
inTests = false // set true by fix_test.go; if false, no need to use testMu
|
||||
testMu sync.RWMutex // guards globals reset by tests; used only if inTests
|
||||
)
|
||||
|
||||
// If set, LocalPrefix instructs Process to sort import paths with the given
|
||||
// prefix into another group after 3rd-party packages.
|
||||
var LocalPrefix string
|
||||
|
||||
// importToGroup is a list of functions which map from an import path to
|
||||
// a group number.
|
||||
var importToGroup = []func(importPath string) (num int, ok bool){
|
||||
func(importPath string) (num int, ok bool) {
|
||||
if LocalPrefix != "" && strings.HasPrefix(importPath, LocalPrefix) {
|
||||
return 3, true
|
||||
}
|
||||
return
|
||||
},
|
||||
func(importPath string) (num int, ok bool) {
|
||||
if strings.HasPrefix(importPath, "appengine") {
|
||||
return 2, true
|
||||
|
@ -58,7 +81,10 @@ func fixImports(fset *token.FileSet, f *ast.File, filename string) (added []stri
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srcDir := path.Dir(abs)
|
||||
srcDir := filepath.Dir(abs)
|
||||
if Debug {
|
||||
log.Printf("fixImports(filename=%q), abs=%q, srcDir=%q ...", filename, abs, srcDir)
|
||||
}
|
||||
|
||||
// collect potential uses of packages.
|
||||
var visitor visitFn
|
||||
|
@ -70,10 +96,14 @@ func fixImports(fset *token.FileSet, f *ast.File, filename string) (added []stri
|
|||
case *ast.ImportSpec:
|
||||
if v.Name != nil {
|
||||
decls[v.Name.Name] = v
|
||||
} else {
|
||||
local := importPathToName(strings.Trim(v.Path.Value, `\"`), srcDir)
|
||||
decls[local] = v
|
||||
break
|
||||
}
|
||||
ipath := strings.Trim(v.Path.Value, `"`)
|
||||
if ipath == "C" {
|
||||
break
|
||||
}
|
||||
local := importPathToName(ipath, srcDir)
|
||||
decls[local] = v
|
||||
case *ast.SelectorExpr:
|
||||
xident, ok := v.X.(*ast.Ident)
|
||||
if !ok {
|
||||
|
@ -114,18 +144,22 @@ func fixImports(fset *token.FileSet, f *ast.File, filename string) (added []stri
|
|||
astutil.DeleteNamedImport(fset, f, name, ipath)
|
||||
}
|
||||
|
||||
for pkgName, symbols := range refs {
|
||||
if len(symbols) == 0 {
|
||||
// skip over packages already imported
|
||||
delete(refs, pkgName)
|
||||
}
|
||||
}
|
||||
|
||||
// Search for imports matching potential package references.
|
||||
searches := 0
|
||||
type result struct {
|
||||
ipath string
|
||||
name string
|
||||
ipath string // import path (if err == nil)
|
||||
name string // optional name to rename import as
|
||||
err error
|
||||
}
|
||||
results := make(chan result)
|
||||
for pkgName, symbols := range refs {
|
||||
if len(symbols) == 0 {
|
||||
continue // skip over packages already imported
|
||||
}
|
||||
go func(pkgName string, symbols map[string]bool) {
|
||||
ipath, rename, err := findImport(pkgName, symbols, filename)
|
||||
r := result{ipath: ipath, err: err}
|
||||
|
@ -155,7 +189,7 @@ func fixImports(fset *token.FileSet, f *ast.File, filename string) (added []stri
|
|||
}
|
||||
|
||||
// importPathToName returns the package name for the given import path.
|
||||
var importPathToName = importPathToNameGoPath
|
||||
var importPathToName func(importPath, srcDir string) (packageName string) = importPathToNameGoPath
|
||||
|
||||
// importPathToNameBasic assumes the package name is the base of import path.
|
||||
func importPathToNameBasic(importPath, srcDir string) (packageName string) {
|
||||
|
@ -165,24 +199,121 @@ func importPathToNameBasic(importPath, srcDir string) (packageName string) {
|
|||
// importPathToNameGoPath finds out the actual package name, as declared in its .go files.
|
||||
// If there's a problem, it falls back to using importPathToNameBasic.
|
||||
func importPathToNameGoPath(importPath, srcDir string) (packageName string) {
|
||||
if buildPkg, err := build.Import(importPath, srcDir, 0); err == nil {
|
||||
return buildPkg.Name
|
||||
} else {
|
||||
// Fast path for standard library without going to disk.
|
||||
if pkg, ok := stdImportPackage[importPath]; ok {
|
||||
return pkg
|
||||
}
|
||||
|
||||
pkgName, err := importPathToNameGoPathParse(importPath, srcDir)
|
||||
if Debug {
|
||||
log.Printf("importPathToNameGoPathParse(%q, srcDir=%q) = %q, %v", importPath, srcDir, pkgName, err)
|
||||
}
|
||||
if err == nil {
|
||||
return pkgName
|
||||
}
|
||||
return importPathToNameBasic(importPath, srcDir)
|
||||
}
|
||||
|
||||
// importPathToNameGoPathParse is a faster version of build.Import if
|
||||
// the only thing desired is the package name. It uses build.FindOnly
|
||||
// to find the directory and then only parses one file in the package,
|
||||
// trusting that the files in the directory are consistent.
|
||||
func importPathToNameGoPathParse(importPath, srcDir string) (packageName string, err error) {
|
||||
buildPkg, err := build.Import(importPath, srcDir, build.FindOnly)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
d, err := os.Open(buildPkg.Dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
names, err := d.Readdirnames(-1)
|
||||
d.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sort.Strings(names) // to have predictable behavior
|
||||
var lastErr error
|
||||
var nfile int
|
||||
for _, name := range names {
|
||||
if !strings.HasSuffix(name, ".go") {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(name, "_test.go") {
|
||||
continue
|
||||
}
|
||||
nfile++
|
||||
fullFile := filepath.Join(buildPkg.Dir, name)
|
||||
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, fullFile, nil, parser.PackageClauseOnly)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
pkgName := f.Name.Name
|
||||
if pkgName == "documentation" {
|
||||
// Special case from go/build.ImportDir, not
|
||||
// handled by ctx.MatchFile.
|
||||
continue
|
||||
}
|
||||
if pkgName == "main" {
|
||||
// Also skip package main, assuming it's a +build ignore generator or example.
|
||||
// Since you can't import a package main anyway, there's no harm here.
|
||||
continue
|
||||
}
|
||||
return pkgName, nil
|
||||
}
|
||||
if lastErr != nil {
|
||||
return "", lastErr
|
||||
}
|
||||
return "", fmt.Errorf("no importable package found in %d Go files", nfile)
|
||||
}
|
||||
|
||||
var stdImportPackage = map[string]string{} // "net/http" => "http"
|
||||
|
||||
func init() {
|
||||
// Nothing in the standard library has a package name not
|
||||
// matching its import base name.
|
||||
for _, pkg := range stdlib {
|
||||
if _, ok := stdImportPackage[pkg]; !ok {
|
||||
stdImportPackage[pkg] = path.Base(pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Directory-scanning state.
|
||||
var (
|
||||
// scanGoRootOnce guards calling scanGoRoot (for $GOROOT)
|
||||
scanGoRootOnce sync.Once
|
||||
// scanGoPathOnce guards calling scanGoPath (for $GOPATH)
|
||||
scanGoPathOnce sync.Once
|
||||
|
||||
// populateIgnoreOnce guards calling populateIgnore
|
||||
populateIgnoreOnce sync.Once
|
||||
ignoredDirs []os.FileInfo
|
||||
|
||||
dirScanMu sync.RWMutex
|
||||
dirScan map[string]*pkg // abs dir path => *pkg
|
||||
)
|
||||
|
||||
type pkg struct {
|
||||
importpath string // full pkg import path, e.g. "net/http"
|
||||
dir string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
|
||||
dir string // absolute file path to pkg directory ("/usr/lib/go/src/net/http")
|
||||
importPath string // full pkg import path ("net/http", "foo/bar/vendor/a/b")
|
||||
importPathShort string // vendorless import path ("net/http", "a/b")
|
||||
}
|
||||
|
||||
var pkgIndexOnce = &sync.Once{}
|
||||
// byImportPathShortLength sorts by the short import path length, breaking ties on the
|
||||
// import string itself.
|
||||
type byImportPathShortLength []*pkg
|
||||
|
||||
func (s byImportPathShortLength) Len() int { return len(s) }
|
||||
func (s byImportPathShortLength) Less(i, j int) bool {
|
||||
vi, vj := s[i].importPathShort, s[j].importPathShort
|
||||
return len(vi) < len(vj) || (len(vi) == len(vj) && vi < vj)
|
||||
|
||||
var pkgIndex struct {
|
||||
sync.Mutex
|
||||
m map[string][]pkg // shortname => []pkg, e.g "http" => "net/http"
|
||||
}
|
||||
func (s byImportPathShortLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// gate is a semaphore for limiting concurrency.
|
||||
type gate chan struct{}
|
||||
|
@ -190,34 +321,79 @@ type gate chan struct{}
|
|||
func (g gate) enter() { g <- struct{}{} }
|
||||
func (g gate) leave() { <-g }
|
||||
|
||||
// fsgate protects the OS & filesystem from too much concurrency.
|
||||
// Too much disk I/O -> too many threads -> swapping and bad scheduling.
|
||||
var fsgate = make(gate, 8)
|
||||
|
||||
var visitedSymlinks struct {
|
||||
sync.Mutex
|
||||
m map[string]struct{}
|
||||
}
|
||||
|
||||
// shouldTraverse checks if fi, found in dir, is a directory or a symlink to a directory.
|
||||
// It makes sure symlinks were never visited before to avoid symlink loops.
|
||||
func shouldTraverse(dir string, fi os.FileInfo) bool {
|
||||
if fi.IsDir() {
|
||||
return true
|
||||
// guarded by populateIgnoreOnce; populates ignoredDirs.
|
||||
func populateIgnore() {
|
||||
for _, srcDir := range build.Default.SrcDirs() {
|
||||
if srcDir == filepath.Join(build.Default.GOROOT, "src") {
|
||||
continue
|
||||
}
|
||||
populateIgnoredDirs(srcDir)
|
||||
}
|
||||
}
|
||||
|
||||
if fi.Mode()&os.ModeSymlink == 0 {
|
||||
// populateIgnoredDirs reads an optional config file at <path>/.goimportsignore
|
||||
// of relative directories to ignore when scanning for go files.
|
||||
// The provided path is one of the $GOPATH entries with "src" appended.
|
||||
func populateIgnoredDirs(path string) {
|
||||
file := filepath.Join(path, ".goimportsignore")
|
||||
slurp, err := ioutil.ReadFile(file)
|
||||
if Debug {
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
} else {
|
||||
log.Printf("Read %s", file)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
bs := bufio.NewScanner(bytes.NewReader(slurp))
|
||||
for bs.Scan() {
|
||||
line := strings.TrimSpace(bs.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
full := filepath.Join(path, line)
|
||||
if fi, err := os.Stat(full); err == nil {
|
||||
ignoredDirs = append(ignoredDirs, fi)
|
||||
if Debug {
|
||||
log.Printf("Directory added to ignore list: %s", full)
|
||||
}
|
||||
} else if Debug {
|
||||
log.Printf("Error statting entry in .goimportsignore: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func skipDir(fi os.FileInfo) bool {
|
||||
for _, ignoredDir := range ignoredDirs {
|
||||
if os.SameFile(fi, ignoredDir) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// shouldTraverse reports whether the symlink fi should, found in dir,
|
||||
// should be followed. It makes sure symlinks were never visited
|
||||
// before to avoid symlink loops.
|
||||
func shouldTraverse(dir string, fi os.FileInfo) bool {
|
||||
path := filepath.Join(dir, fi.Name())
|
||||
target, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
if !os.IsNotExist(err) {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
ts, err := os.Stat(target)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return false
|
||||
}
|
||||
if !ts.IsDir() {
|
||||
|
@ -242,190 +418,415 @@ func shouldTraverse(dir string, fi os.FileInfo) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func loadPkgIndex() {
|
||||
pkgIndex.Lock()
|
||||
pkgIndex.m = make(map[string][]pkg)
|
||||
pkgIndex.Unlock()
|
||||
var testHookScanDir = func(dir string) {}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, path := range build.Default.SrcDirs() {
|
||||
fsgate.enter()
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
fsgate.leave()
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
continue
|
||||
}
|
||||
children, err := f.Readdir(-1)
|
||||
f.Close()
|
||||
fsgate.leave()
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
continue
|
||||
}
|
||||
for _, child := range children {
|
||||
if shouldTraverse(path, child) {
|
||||
wg.Add(1)
|
||||
go func(path, name string) {
|
||||
defer wg.Done()
|
||||
loadPkg(&wg, path, name)
|
||||
}(path, child.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
var scanGoRootDone = make(chan struct{}) // closed when scanGoRoot is done
|
||||
|
||||
func scanGoRoot() {
|
||||
go func() {
|
||||
scanGoDirs(true)
|
||||
close(scanGoRootDone)
|
||||
}()
|
||||
}
|
||||
|
||||
func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) {
|
||||
importpath := filepath.ToSlash(pkgrelpath)
|
||||
dir := filepath.Join(root, importpath)
|
||||
func scanGoPath() { scanGoDirs(false) }
|
||||
|
||||
fsgate.enter()
|
||||
defer fsgate.leave()
|
||||
pkgDir, err := os.Open(dir)
|
||||
if err != nil {
|
||||
return
|
||||
func scanGoDirs(goRoot bool) {
|
||||
if Debug {
|
||||
which := "$GOROOT"
|
||||
if !goRoot {
|
||||
which = "$GOPATH"
|
||||
}
|
||||
children, err := pkgDir.Readdir(-1)
|
||||
pkgDir.Close()
|
||||
if err != nil {
|
||||
return
|
||||
log.Printf("scanning " + which)
|
||||
defer log.Printf("scanned " + which)
|
||||
}
|
||||
// hasGo tracks whether a directory actually appears to be a
|
||||
// Go source code directory. If $GOPATH == $HOME, and
|
||||
// $HOME/src has lots of other large non-Go projects in it,
|
||||
// then the calls to importPathToName below can be expensive.
|
||||
hasGo := false
|
||||
for _, child := range children {
|
||||
// Avoid .foo, _foo, and testdata directory trees.
|
||||
name := child.Name()
|
||||
if name == "" || name[0] == '.' || name[0] == '_' || name == "testdata" {
|
||||
dirScanMu.Lock()
|
||||
if dirScan == nil {
|
||||
dirScan = make(map[string]*pkg)
|
||||
}
|
||||
dirScanMu.Unlock()
|
||||
|
||||
for _, srcDir := range build.Default.SrcDirs() {
|
||||
isGoroot := srcDir == filepath.Join(build.Default.GOROOT, "src")
|
||||
if isGoroot != goRoot {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(name, ".go") {
|
||||
hasGo = true
|
||||
testHookScanDir(srcDir)
|
||||
walkFn := func(path string, typ os.FileMode) error {
|
||||
dir := filepath.Dir(path)
|
||||
if typ.IsRegular() {
|
||||
if dir == srcDir {
|
||||
// Doesn't make sense to have regular files
|
||||
// directly in your $GOPATH/src or $GOROOT/src.
|
||||
return nil
|
||||
}
|
||||
if shouldTraverse(dir, child) {
|
||||
wg.Add(1)
|
||||
go func(root, name string) {
|
||||
defer wg.Done()
|
||||
loadPkg(wg, root, name)
|
||||
}(root, filepath.Join(importpath, name))
|
||||
if !strings.HasSuffix(path, ".go") {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if hasGo {
|
||||
shortName := importPathToName(importpath, "")
|
||||
pkgIndex.Lock()
|
||||
pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{
|
||||
importpath: importpath,
|
||||
dirScanMu.Lock()
|
||||
if _, dup := dirScan[dir]; !dup {
|
||||
importpath := filepath.ToSlash(dir[len(srcDir)+len("/"):])
|
||||
dirScan[dir] = &pkg{
|
||||
importPath: importpath,
|
||||
importPathShort: vendorlessImportPath(importpath),
|
||||
dir: dir,
|
||||
})
|
||||
pkgIndex.Unlock()
|
||||
}
|
||||
}
|
||||
dirScanMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
if typ == os.ModeDir {
|
||||
base := filepath.Base(path)
|
||||
if base == "" || base[0] == '.' || base[0] == '_' ||
|
||||
base == "testdata" || base == "node_modules" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
fi, err := os.Lstat(path)
|
||||
if err == nil && skipDir(fi) {
|
||||
if Debug {
|
||||
log.Printf("skipping directory %q under %s", fi.Name(), dir)
|
||||
}
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if typ == os.ModeSymlink {
|
||||
base := filepath.Base(path)
|
||||
if strings.HasPrefix(base, ".#") {
|
||||
// Emacs noise.
|
||||
return nil
|
||||
}
|
||||
fi, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
// Just ignore it.
|
||||
return nil
|
||||
}
|
||||
if shouldTraverse(dir, fi) {
|
||||
return traverseLink
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := fastWalk(srcDir, walkFn); err != nil {
|
||||
log.Printf("goimports: scanning directory %v: %v", srcDir, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vendorlessImportPath returns the devendorized version of the provided import path.
|
||||
// e.g. "foo/bar/vendor/a/b" => "a/b"
|
||||
func vendorlessImportPath(ipath string) string {
|
||||
// Devendorize for use in import statement.
|
||||
if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 {
|
||||
return ipath[i+len("/vendor/"):]
|
||||
}
|
||||
if strings.HasPrefix(ipath, "vendor/") {
|
||||
return ipath[len("vendor/"):]
|
||||
}
|
||||
return ipath
|
||||
}
|
||||
|
||||
// loadExports returns a list exports for a package.
|
||||
var loadExports = loadExportsGoPath
|
||||
// loadExports returns the set of exported symbols in the package at dir.
|
||||
// It returns nil on error or if the package name in dir does not match expectPackage.
|
||||
var loadExports func(expectPackage, dir string) map[string]bool = loadExportsGoPath
|
||||
|
||||
func loadExportsGoPath(dir string) map[string]bool {
|
||||
func loadExportsGoPath(expectPackage, dir string) map[string]bool {
|
||||
if Debug {
|
||||
log.Printf("loading exports in dir %s (seeking package %s)", dir, expectPackage)
|
||||
}
|
||||
exports := make(map[string]bool)
|
||||
buildPkg, err := build.ImportDir(dir, 0)
|
||||
|
||||
ctx := build.Default
|
||||
|
||||
// ReadDir is like ioutil.ReadDir, but only returns *.go files
|
||||
// and filters out _test.go files since they're not relevant
|
||||
// and only slow things down.
|
||||
ctx.ReadDir = func(dir string) (notTests []os.FileInfo, err error) {
|
||||
all, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no buildable Go source files in") {
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "could not import %q: %v\n", dir, err)
|
||||
return nil, err
|
||||
}
|
||||
notTests = all[:0]
|
||||
for _, fi := range all {
|
||||
name := fi.Name()
|
||||
if strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go") {
|
||||
notTests = append(notTests, fi)
|
||||
}
|
||||
}
|
||||
return notTests, nil
|
||||
}
|
||||
|
||||
files, err := ctx.ReadDir(dir)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
fset := token.NewFileSet()
|
||||
for _, files := range [...][]string{buildPkg.GoFiles, buildPkg.CgoFiles} {
|
||||
for _, file := range files {
|
||||
f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "could not parse %q: %v\n", file, err)
|
||||
|
||||
for _, fi := range files {
|
||||
match, err := ctx.MatchFile(dir, fi.Name())
|
||||
if err != nil || !match {
|
||||
continue
|
||||
}
|
||||
fullFile := filepath.Join(dir, fi.Name())
|
||||
f, err := parser.ParseFile(fset, fullFile, nil, 0)
|
||||
if err != nil {
|
||||
if Debug {
|
||||
log.Printf("Parsing %s: %v", fullFile, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
pkgName := f.Name.Name
|
||||
if pkgName == "documentation" {
|
||||
// Special case from go/build.ImportDir, not
|
||||
// handled by ctx.MatchFile.
|
||||
continue
|
||||
}
|
||||
if pkgName != expectPackage {
|
||||
if Debug {
|
||||
log.Printf("scan of dir %v is not expected package %v (actually %v)", dir, expectPackage, pkgName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for name := range f.Scope.Objects {
|
||||
if ast.IsExported(name) {
|
||||
exports[name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if Debug {
|
||||
exportList := make([]string, 0, len(exports))
|
||||
for k := range exports {
|
||||
exportList = append(exportList, k)
|
||||
}
|
||||
sort.Strings(exportList)
|
||||
log.Printf("loaded exports in dir %v (package %v): %v", dir, expectPackage, strings.Join(exportList, ", "))
|
||||
}
|
||||
return exports
|
||||
}
|
||||
|
||||
// findImport searches for a package with the given symbols.
|
||||
// If no package is found, findImport returns "".
|
||||
// Declared as a variable rather than a function so goimports can be easily
|
||||
// extended by adding a file with an init function.
|
||||
var findImport = findImportGoPath
|
||||
// If no package is found, findImport returns ("", false, nil)
|
||||
//
|
||||
// This is declared as a variable rather than a function so goimports
|
||||
// can be easily extended by adding a file with an init function.
|
||||
//
|
||||
// The rename value tells goimports whether to use the package name as
|
||||
// a local qualifier in an import. For example, if findImports("pkg",
|
||||
// "X") returns ("foo/bar", rename=true), then goimports adds the
|
||||
// import line:
|
||||
// import pkg "foo/bar"
|
||||
// to satisfy uses of pkg.X in the file.
|
||||
var findImport func(pkgName string, symbols map[string]bool, filename string) (foundPkg string, rename bool, err error) = findImportGoPath
|
||||
|
||||
// findImportGoPath is the normal implementation of findImport.
|
||||
// (Some companies have their own internally.)
|
||||
func findImportGoPath(pkgName string, symbols map[string]bool, filename string) (foundPkg string, rename bool, err error) {
|
||||
if inTests {
|
||||
testMu.RLock()
|
||||
defer testMu.RUnlock()
|
||||
}
|
||||
|
||||
func findImportGoPath(pkgName string, symbols map[string]bool, filename string) (string, bool, error) {
|
||||
// Fast path for the standard library.
|
||||
// In the common case we hopefully never have to scan the GOPATH, which can
|
||||
// be slow with moving disks.
|
||||
if pkg, rename, ok := findImportStdlib(pkgName, symbols); ok {
|
||||
return pkg, rename, nil
|
||||
}
|
||||
if pkgName == "rand" && symbols["Read"] {
|
||||
// Special-case rand.Read.
|
||||
//
|
||||
// If findImportStdlib didn't find it above, don't go
|
||||
// searching for it, lest it find and pick math/rand
|
||||
// in GOROOT (new as of Go 1.6)
|
||||
//
|
||||
// crypto/rand is the safer choice.
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
// TODO(sameer): look at the import lines for other Go files in the
|
||||
// local directory, since the user is likely to import the same packages
|
||||
// in the current Go file. Return rename=true when the other Go files
|
||||
// use a renamed package that's also used in the current file.
|
||||
|
||||
pkgIndexOnce.Do(loadPkgIndex)
|
||||
// Read all the $GOPATH/src/.goimportsignore files before scanning directories.
|
||||
populateIgnoreOnce.Do(populateIgnore)
|
||||
|
||||
// Start scanning the $GOROOT asynchronously, then run the
|
||||
// GOPATH scan synchronously if needed, and then wait for the
|
||||
// $GOROOT to finish.
|
||||
//
|
||||
// TODO(bradfitz): run each $GOPATH entry async. But nobody
|
||||
// really has more than one anyway, so low priority.
|
||||
scanGoRootOnce.Do(scanGoRoot) // async
|
||||
if !fileInDir(filename, build.Default.GOROOT) {
|
||||
scanGoPathOnce.Do(scanGoPath) // blocking
|
||||
}
|
||||
<-scanGoRootDone
|
||||
|
||||
// Find candidate packages, looking only at their directory names first.
|
||||
var candidates []*pkg
|
||||
for _, pkg := range dirScan {
|
||||
if pkgIsCandidate(filename, pkgName, pkg) {
|
||||
candidates = append(candidates, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the candidates by their import package length,
|
||||
// assuming that shorter package names are better than long
|
||||
// ones. Note that this sorts by the de-vendored name, so
|
||||
// there's no "penalty" for vendoring.
|
||||
sort.Sort(byImportPathShortLength(candidates))
|
||||
if Debug {
|
||||
for i, pkg := range candidates {
|
||||
log.Printf("%s candidate %d/%d: %v", pkgName, i+1, len(candidates), pkg.importPathShort)
|
||||
}
|
||||
}
|
||||
|
||||
// Collect exports for packages with matching names.
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
shortest string
|
||||
)
|
||||
pkgIndex.Lock()
|
||||
for _, pkg := range pkgIndex.m[pkgName] {
|
||||
if !canUse(filename, pkg.dir) {
|
||||
continue
|
||||
|
||||
done := make(chan struct{}) // closed when we find the answer
|
||||
defer close(done)
|
||||
|
||||
rescv := make([]chan *pkg, len(candidates))
|
||||
for i := range candidates {
|
||||
rescv[i] = make(chan *pkg)
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(importpath, dir string) {
|
||||
defer wg.Done()
|
||||
exports := loadExports(dir)
|
||||
if exports == nil {
|
||||
const maxConcurrentPackageImport = 4
|
||||
loadExportsSem := make(chan struct{}, maxConcurrentPackageImport)
|
||||
|
||||
go func() {
|
||||
for i, pkg := range candidates {
|
||||
select {
|
||||
case loadExportsSem <- struct{}{}:
|
||||
select {
|
||||
case <-done:
|
||||
default:
|
||||
}
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
// If it doesn't have the right symbols, stop.
|
||||
pkg := pkg
|
||||
resc := rescv[i]
|
||||
go func() {
|
||||
if inTests {
|
||||
testMu.RLock()
|
||||
defer testMu.RUnlock()
|
||||
}
|
||||
defer func() { <-loadExportsSem }()
|
||||
exports := loadExports(pkgName, pkg.dir)
|
||||
|
||||
// If it doesn't have the right
|
||||
// symbols, send nil to mean no match.
|
||||
for symbol := range symbols {
|
||||
if !exports[symbol] {
|
||||
return
|
||||
pkg = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
select {
|
||||
case resc <- pkg:
|
||||
case <-done:
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
for _, resc := range rescv {
|
||||
pkg := <-resc
|
||||
if pkg == nil {
|
||||
continue
|
||||
}
|
||||
// If the package name in the source doesn't match the import path's base,
|
||||
// return true so the rewriter adds a name (import foo "github.com/bar/go-foo")
|
||||
needsRename := path.Base(pkg.importPath) != pkgName
|
||||
return pkg.importPathShort, needsRename, nil
|
||||
}
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
// pkgIsCandidate reports whether pkg is a candidate for satisfying the
|
||||
// finding which package pkgIdent in the file named by filename is trying
|
||||
// to refer to.
|
||||
//
|
||||
// This check is purely lexical and is meant to be as fast as possible
|
||||
// because it's run over all $GOPATH directories to filter out poor
|
||||
// candidates in order to limit the CPU and I/O later parsing the
|
||||
// exports in candidate packages.
|
||||
//
|
||||
// filename is the file being formatted.
|
||||
// pkgIdent is the package being searched for, like "client" (if
|
||||
// searching for "client.New")
|
||||
func pkgIsCandidate(filename, pkgIdent string, pkg *pkg) bool {
|
||||
// Check "internal" and "vendor" visibility:
|
||||
if !canUse(filename, pkg.dir) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Speed optimization to minimize disk I/O:
|
||||
// the last two components on disk must contain the
|
||||
// package name somewhere.
|
||||
//
|
||||
// This permits mismatch naming like directory
|
||||
// "go-foo" being package "foo", or "pkg.v3" being "pkg",
|
||||
// or directory "google.golang.org/api/cloudbilling/v1"
|
||||
// being package "cloudbilling", but doesn't
|
||||
// permit a directory "foo" to be package
|
||||
// "bar", which is strongly discouraged
|
||||
// anyway. There's no reason goimports needs
|
||||
// to be slow just to accomodate that.
|
||||
lastTwo := lastTwoComponents(pkg.importPathShort)
|
||||
if strings.Contains(lastTwo, pkgIdent) {
|
||||
return true
|
||||
}
|
||||
if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(pkgIdent) {
|
||||
lastTwo = lowerASCIIAndRemoveHyphen(lastTwo)
|
||||
if strings.Contains(lastTwo, pkgIdent) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Devendorize for use in import statement.
|
||||
if i := strings.LastIndex(importpath, "/vendor/"); i >= 0 {
|
||||
importpath = importpath[i+len("/vendor/"):]
|
||||
} else if strings.HasPrefix(importpath, "vendor/") {
|
||||
importpath = importpath[len("vendor/"):]
|
||||
return false
|
||||
}
|
||||
|
||||
// Save as the answer.
|
||||
// If there are multiple candidates, the shortest wins,
|
||||
// to prefer "bytes" over "github.com/foo/bytes".
|
||||
mu.Lock()
|
||||
if shortest == "" || len(importpath) < len(shortest) || len(importpath) == len(shortest) && importpath < shortest {
|
||||
shortest = importpath
|
||||
func hasHyphenOrUpperASCII(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
b := s[i]
|
||||
if b == '-' || ('A' <= b && b <= 'Z') {
|
||||
return true
|
||||
}
|
||||
mu.Unlock()
|
||||
}(pkg.importpath, pkg.dir)
|
||||
}
|
||||
pkgIndex.Unlock()
|
||||
wg.Wait()
|
||||
|
||||
return shortest, false, nil
|
||||
return false
|
||||
}
|
||||
|
||||
func lowerASCIIAndRemoveHyphen(s string) (ret string) {
|
||||
buf := make([]byte, 0, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch {
|
||||
case b == '-':
|
||||
continue
|
||||
case 'A' <= b && b <= 'Z':
|
||||
buf = append(buf, b+('a'-'A'))
|
||||
default:
|
||||
buf = append(buf, b)
|
||||
}
|
||||
}
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
// canUse reports whether the package in dir is usable from filename,
|
||||
// respecting the Go "internal" and "vendor" visibility rules.
|
||||
func canUse(filename, dir string) bool {
|
||||
// Fast path check, before any allocations. If it doesn't contain vendor
|
||||
// or internal, it's not tricky:
|
||||
// Note that this can false-negative on directories like "notinternal",
|
||||
// but we check it correctly below. This is just a fast path.
|
||||
if !strings.Contains(dir, "vendor") && !strings.Contains(dir, "internal") {
|
||||
return true
|
||||
}
|
||||
|
||||
dirSlash := filepath.ToSlash(dir)
|
||||
if !strings.Contains(dirSlash, "/vendor/") && !strings.Contains(dirSlash, "/internal/") && !strings.HasSuffix(dirSlash, "/internal") {
|
||||
return true
|
||||
|
@ -436,11 +837,15 @@ func canUse(filename, dir string) bool {
|
|||
// or bar/vendor or bar/internal.
|
||||
// After stripping all the leading ../, the only okay place to see vendor or internal
|
||||
// is at the very beginning of the path.
|
||||
abs, err := filepath.Abs(filename)
|
||||
absfile, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
rel, err := filepath.Rel(abs, dir)
|
||||
absdir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
rel, err := filepath.Rel(absfile, absdir)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
@ -451,6 +856,21 @@ func canUse(filename, dir string) bool {
|
|||
return !strings.Contains(relSlash, "/vendor/") && !strings.Contains(relSlash, "/internal/") && !strings.HasSuffix(relSlash, "/internal")
|
||||
}
|
||||
|
||||
// lastTwoComponents returns at most the last two path components
|
||||
// of v, using either / or \ as the path separator.
|
||||
func lastTwoComponents(v string) string {
|
||||
nslash := 0
|
||||
for i := len(v) - 1; i >= 0; i-- {
|
||||
if v[i] == '/' || v[i] == '\\' {
|
||||
nslash++
|
||||
if nslash == 2 {
|
||||
return v[i:]
|
||||
}
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
type visitFn func(node ast.Node) ast.Visitor
|
||||
|
||||
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
|
||||
|
@ -459,8 +879,12 @@ func (fn visitFn) Visit(node ast.Node) ast.Visitor {
|
|||
|
||||
func findImportStdlib(shortPkg string, symbols map[string]bool) (importPath string, rename, ok bool) {
|
||||
for symbol := range symbols {
|
||||
path := stdlib[shortPkg+"."+symbol]
|
||||
key := shortPkg + "." + symbol
|
||||
path := stdlib[key]
|
||||
if path == "" {
|
||||
if key == "rand.Read" {
|
||||
continue
|
||||
}
|
||||
return "", false, false
|
||||
}
|
||||
if importPath != "" && importPath != path {
|
||||
|
@ -469,5 +893,20 @@ func findImportStdlib(shortPkg string, symbols map[string]bool) (importPath stri
|
|||
}
|
||||
importPath = path
|
||||
}
|
||||
if importPath == "" && shortPkg == "rand" && symbols["Read"] {
|
||||
return "crypto/rand", false, true
|
||||
}
|
||||
return importPath, false, importPath != ""
|
||||
}
|
||||
|
||||
// fileInDir reports whether the provided file path looks like
|
||||
// it's in dir. (without hitting the filesystem)
|
||||
func fileInDir(file, dir string) bool {
|
||||
rest := strings.TrimPrefix(file, dir)
|
||||
if len(rest) == len(file) {
|
||||
// dir is not a prefix of file.
|
||||
return false
|
||||
}
|
||||
// Check for boundary: either nothing (file == dir), or a slash.
|
||||
return len(rest) == 0 || rest[0] == '/' || rest[0] == '\\'
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
@ -826,8 +827,9 @@ func TestFixImports(t *testing.T) {
|
|||
// Test support for packages in GOPATH that are actually symlinks.
|
||||
// Also test that a symlink loop does not block the process.
|
||||
func TestImportSymlinks(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Skipping test on Windows as there are no symlinks.")
|
||||
switch runtime.GOOS {
|
||||
case "windows", "plan9":
|
||||
t.Skipf("skipping test on %q as there are no symlinks", runtime.GOOS)
|
||||
}
|
||||
|
||||
newGoPath, err := ioutil.TempDir("", "symlinktest")
|
||||
|
@ -857,13 +859,8 @@ func TestImportSymlinks(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pkgIndexOnce = &sync.Once{}
|
||||
oldGOPATH := build.Default.GOPATH
|
||||
withEmptyGoPath(func() {
|
||||
build.Default.GOPATH = newGoPath
|
||||
defer func() {
|
||||
build.Default.GOPATH = oldGOPATH
|
||||
visitedSymlinks.m = nil
|
||||
}()
|
||||
|
||||
input := `package p
|
||||
|
||||
|
@ -891,6 +888,7 @@ var (
|
|||
if got := string(buf); got != output {
|
||||
t.Fatalf("results differ\nGOT:\n%s\nWANT:\n%s\n", got, output)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test for correctly identifying the name of a vendored package when it
|
||||
|
@ -902,29 +900,11 @@ func TestFixImportsVendorPackage(t *testing.T) {
|
|||
if _, err := os.Stat(filepath.Join(runtime.GOROOT(), "src/vendor")); err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
|
||||
newGoPath, err := ioutil.TempDir("", "vendortest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(newGoPath)
|
||||
|
||||
vendoredPath := newGoPath + "/src/mypkg.com/outpkg/vendor/mypkg.com/mypkg.v1"
|
||||
if err := os.MkdirAll(vendoredPath, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pkgIndexOnce = &sync.Once{}
|
||||
oldGOPATH := build.Default.GOPATH
|
||||
build.Default.GOPATH = newGoPath
|
||||
defer func() {
|
||||
build.Default.GOPATH = oldGOPATH
|
||||
}()
|
||||
|
||||
if err := ioutil.WriteFile(vendoredPath+"/f.go", []byte("package mypkg\nvar Foo = 123\n"), 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testConfig{
|
||||
gopathFiles: map[string]string{
|
||||
"mypkg.com/outpkg/vendor/mypkg.com/mypkg.v1/f.go": "package mypkg\nvar Foo = 123\n",
|
||||
},
|
||||
}.test(t, func(t *goimportTest) {
|
||||
input := `package p
|
||||
|
||||
import (
|
||||
|
@ -938,13 +918,14 @@ var (
|
|||
_ = mypkg.Foo
|
||||
)
|
||||
`
|
||||
buf, err := Process(newGoPath+"/src/mypkg.com/outpkg/toformat.go", []byte(input), &Options{})
|
||||
buf, err := Process(filepath.Join(t.gopath, "src/mypkg.com/outpkg/toformat.go"), []byte(input), &Options{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := string(buf); got != input {
|
||||
t.Fatalf("results differ\nGOT:\n%s\nWANT:\n%s\n", got, input)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFindImportGoPath(t *testing.T) {
|
||||
|
@ -954,14 +935,13 @@ func TestFindImportGoPath(t *testing.T) {
|
|||
}
|
||||
defer os.RemoveAll(goroot)
|
||||
|
||||
pkgIndexOnce = &sync.Once{}
|
||||
|
||||
origStdlib := stdlib
|
||||
defer func() {
|
||||
stdlib = origStdlib
|
||||
}()
|
||||
stdlib = nil
|
||||
|
||||
withEmptyGoPath(func() {
|
||||
// Test against imaginary bits/bytes package in std lib
|
||||
bytesDir := filepath.Join(goroot, "src", "pkg", "bits", "bytes")
|
||||
for _, tag := range build.Default.ReleaseTags {
|
||||
|
@ -982,14 +962,7 @@ type Buffer2 struct {}
|
|||
if err := ioutil.WriteFile(bytesSrcPath, bytesSrc, 0775); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
oldGOROOT := build.Default.GOROOT
|
||||
oldGOPATH := build.Default.GOPATH
|
||||
build.Default.GOROOT = goroot
|
||||
build.Default.GOPATH = ""
|
||||
defer func() {
|
||||
build.Default.GOROOT = oldGOROOT
|
||||
build.Default.GOPATH = oldGOPATH
|
||||
}()
|
||||
|
||||
got, rename, err := findImportGoPath("bytes", map[string]bool{"Buffer2": true}, "x.go")
|
||||
if err != nil {
|
||||
|
@ -1006,16 +979,45 @@ type Buffer2 struct {}
|
|||
if got != "" || rename {
|
||||
t.Errorf(`findImportGoPath("bytes", Missing ...)=%q, %t, want "", false`, got, rename)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
inTests = true
|
||||
}
|
||||
|
||||
func withEmptyGoPath(fn func()) {
|
||||
testMu.Lock()
|
||||
|
||||
dirScanMu.Lock()
|
||||
populateIgnoreOnce = sync.Once{}
|
||||
scanGoRootOnce = sync.Once{}
|
||||
scanGoPathOnce = sync.Once{}
|
||||
dirScan = nil
|
||||
ignoredDirs = nil
|
||||
scanGoRootDone = make(chan struct{})
|
||||
dirScanMu.Unlock()
|
||||
|
||||
oldGOPATH := build.Default.GOPATH
|
||||
oldGOROOT := build.Default.GOROOT
|
||||
build.Default.GOPATH = ""
|
||||
visitedSymlinks.m = nil
|
||||
testHookScanDir = func(string) {}
|
||||
testMu.Unlock()
|
||||
|
||||
defer func() {
|
||||
testMu.Lock()
|
||||
testHookScanDir = func(string) {}
|
||||
build.Default.GOPATH = oldGOPATH
|
||||
build.Default.GOROOT = oldGOROOT
|
||||
testMu.Unlock()
|
||||
}()
|
||||
|
||||
fn()
|
||||
}
|
||||
|
||||
func TestFindImportInternal(t *testing.T) {
|
||||
pkgIndexOnce = &sync.Once{}
|
||||
oldGOPATH := build.Default.GOPATH
|
||||
build.Default.GOPATH = ""
|
||||
defer func() {
|
||||
build.Default.GOPATH = oldGOPATH
|
||||
}()
|
||||
|
||||
withEmptyGoPath(func() {
|
||||
// Check for src/internal/race, not just src/internal,
|
||||
// so that we can run this test also against go1.5
|
||||
// (which doesn't contain that file).
|
||||
|
@ -1029,7 +1031,7 @@ func TestFindImportInternal(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
if got != "internal/race" || rename {
|
||||
t.Errorf(`findImportGoPath("race", Acquire ...)=%q, %t, want "internal/race", false`, got, rename)
|
||||
t.Errorf(`findImportGoPath("race", Acquire ...) = %q, %t; want "internal/race", false`, got, rename)
|
||||
}
|
||||
|
||||
// should not be able to use internal from outside that tree
|
||||
|
@ -1040,53 +1042,70 @@ func TestFindImportInternal(t *testing.T) {
|
|||
if got != "" || rename {
|
||||
t.Errorf(`findImportGoPath("race", Acquire ...)=%q, %t, want "", false`, got, rename)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// rand.Read should prefer crypto/rand.Read, not math/rand.Read.
|
||||
func TestFindImportRandRead(t *testing.T) {
|
||||
withEmptyGoPath(func() {
|
||||
file := filepath.Join(runtime.GOROOT(), "src/foo/x.go") // dummy
|
||||
tests := []struct {
|
||||
syms []string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
syms: []string{"Read"},
|
||||
want: "crypto/rand",
|
||||
},
|
||||
{
|
||||
syms: []string{"Read", "NewZipf"},
|
||||
want: "math/rand",
|
||||
},
|
||||
{
|
||||
syms: []string{"NewZipf"},
|
||||
want: "math/rand",
|
||||
},
|
||||
{
|
||||
syms: []string{"Read", "Prime"},
|
||||
want: "crypto/rand",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
m := map[string]bool{}
|
||||
for _, sym := range tt.syms {
|
||||
m[sym] = true
|
||||
}
|
||||
got, _, err := findImportGoPath("rand", m, file)
|
||||
if err != nil {
|
||||
t.Errorf("for %q: %v", tt.syms, err)
|
||||
continue
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("for %q, findImportGoPath = %q; want %q", tt.syms, got, tt.want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFindImportVendor(t *testing.T) {
|
||||
pkgIndexOnce = &sync.Once{}
|
||||
oldGOPATH := build.Default.GOPATH
|
||||
build.Default.GOPATH = ""
|
||||
defer func() {
|
||||
build.Default.GOPATH = oldGOPATH
|
||||
}()
|
||||
|
||||
_, err := os.Stat(filepath.Join(runtime.GOROOT(), "src/vendor"))
|
||||
if err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
|
||||
got, rename, err := findImportGoPath("hpack", map[string]bool{"HuffmanDecode": true}, filepath.Join(runtime.GOROOT(), "src/math/x.go"))
|
||||
testConfig{
|
||||
gorootFiles: map[string]string{
|
||||
"vendor/golang.org/x/net/http2/hpack/huffman.go": "package hpack\nfunc HuffmanDecode() { }\n",
|
||||
},
|
||||
}.test(t, func(t *goimportTest) {
|
||||
got, rename, err := findImportGoPath("hpack", map[string]bool{"HuffmanDecode": true}, filepath.Join(t.goroot, "src/math/x.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := "golang.org/x/net/http2/hpack"
|
||||
// Pre-1.7, we temporarily had this package under "internal" - adjust want accordingly.
|
||||
_, err = os.Stat(filepath.Join(runtime.GOROOT(), "src/vendor", want))
|
||||
if err != nil {
|
||||
want = filepath.Join("internal", want)
|
||||
}
|
||||
if got != want || rename {
|
||||
t.Errorf(`findImportGoPath("hpack", HuffmanDecode ...)=%q, %t, want %q, false`, got, rename, want)
|
||||
}
|
||||
|
||||
// should not be able to use vendor from outside that tree
|
||||
got, rename, err = findImportGoPath("hpack", map[string]bool{"HuffmanDecode": true}, filepath.Join(runtime.GOROOT(), "x.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != "" || rename {
|
||||
t.Errorf(`findImportGoPath("hpack", HuffmanDecode ...)=%q, %t, want "", false`, got, rename)
|
||||
t.Errorf(`findImportGoPath("hpack", HuffmanDecode ...) = %q, %t; want %q, false`, got, rename, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestProcessVendor(t *testing.T) {
|
||||
pkgIndexOnce = &sync.Once{}
|
||||
oldGOPATH := build.Default.GOPATH
|
||||
build.Default.GOPATH = ""
|
||||
defer func() {
|
||||
build.Default.GOPATH = oldGOPATH
|
||||
}()
|
||||
|
||||
withEmptyGoPath(func() {
|
||||
_, err := os.Stat(filepath.Join(runtime.GOROOT(), "src/vendor"))
|
||||
if err != nil {
|
||||
t.Skip(err)
|
||||
|
@ -1098,10 +1117,16 @@ func TestProcessVendor(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := "golang.org/x/net/http2/hpack"
|
||||
if !bytes.Contains(out, []byte(want)) {
|
||||
t.Fatalf("Process(%q) did not add expected hpack import:\n%s", target, out)
|
||||
|
||||
want := "golang_org/x/net/http2/hpack"
|
||||
if _, err := os.Stat(filepath.Join(runtime.GOROOT(), "src/vendor", want)); os.IsNotExist(err) {
|
||||
want = "golang.org/x/net/http2/hpack"
|
||||
}
|
||||
|
||||
if !bytes.Contains(out, []byte(want)) {
|
||||
t.Fatalf("Process(%q) did not add expected hpack import %q; got:\n%s", target, want, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFindImportStdlib(t *testing.T) {
|
||||
|
@ -1127,6 +1152,282 @@ func TestFindImportStdlib(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type testConfig struct {
|
||||
// goroot and gopath optionally specifies the path on disk
|
||||
// to use for the GOROOT and GOPATH. If empty, a temp directory
|
||||
// is made if needed.
|
||||
goroot, gopath string
|
||||
|
||||
// gorootFiles optionally specifies the complete contents of GOROOT to use,
|
||||
// If nil, the normal current $GOROOT is used.
|
||||
gorootFiles map[string]string // paths relative to $GOROOT/src to contents
|
||||
|
||||
// gopathFiles is like gorootFiles, but for $GOPATH.
|
||||
// If nil, there is no GOPATH, though.
|
||||
gopathFiles map[string]string // paths relative to $GOPATH/src to contents
|
||||
}
|
||||
|
||||
func mustTempDir(t *testing.T, prefix string) string {
|
||||
dir, err := ioutil.TempDir("", prefix)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func mapToDir(destDir string, files map[string]string) error {
|
||||
for path, contents := range files {
|
||||
file := filepath.Join(destDir, "src", path)
|
||||
if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
if strings.HasPrefix(contents, "LINK:") {
|
||||
err = os.Symlink(strings.TrimPrefix(contents, "LINK:"), file)
|
||||
} else {
|
||||
err = ioutil.WriteFile(file, []byte(contents), 0644)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c testConfig) test(t *testing.T, fn func(*goimportTest)) {
|
||||
goroot := c.goroot
|
||||
gopath := c.gopath
|
||||
|
||||
if c.gorootFiles != nil && goroot == "" {
|
||||
goroot = mustTempDir(t, "goroot-")
|
||||
defer os.RemoveAll(goroot)
|
||||
}
|
||||
if err := mapToDir(goroot, c.gorootFiles); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.gopathFiles != nil && gopath == "" {
|
||||
gopath = mustTempDir(t, "gopath-")
|
||||
defer os.RemoveAll(gopath)
|
||||
}
|
||||
if err := mapToDir(gopath, c.gopathFiles); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
withEmptyGoPath(func() {
|
||||
if goroot != "" {
|
||||
build.Default.GOROOT = goroot
|
||||
}
|
||||
build.Default.GOPATH = gopath
|
||||
|
||||
it := &goimportTest{
|
||||
T: t,
|
||||
goroot: build.Default.GOROOT,
|
||||
gopath: gopath,
|
||||
ctx: &build.Default,
|
||||
}
|
||||
fn(it)
|
||||
})
|
||||
}
|
||||
|
||||
type goimportTest struct {
|
||||
*testing.T
|
||||
ctx *build.Context
|
||||
goroot string
|
||||
gopath string
|
||||
}
|
||||
|
||||
// Tests that added imports are renamed when the import path's base doesn't
|
||||
// match its package name. For example, we want to generate:
|
||||
//
|
||||
// import cloudbilling "google.golang.org/api/cloudbilling/v1"
|
||||
func TestRenameWhenPackageNameMismatch(t *testing.T) {
|
||||
testConfig{
|
||||
gopathFiles: map[string]string{
|
||||
"foo/bar/v1/x.go": "package bar \n const X = 1",
|
||||
},
|
||||
}.test(t, func(t *goimportTest) {
|
||||
buf, err := Process(t.gopath+"/src/test/t.go", []byte("package main \n const Y = bar.X"), &Options{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const want = `package main
|
||||
|
||||
import bar "foo/bar/v1"
|
||||
|
||||
const Y = bar.X
|
||||
`
|
||||
if string(buf) != want {
|
||||
t.Errorf("Got:\n%s\nWant:\n%s", buf, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Tests that the LocalPrefix option causes imports
|
||||
// to be added into a later group (num=3).
|
||||
func TestLocalPrefix(t *testing.T) {
|
||||
defer func(s string) { LocalPrefix = s }(LocalPrefix)
|
||||
LocalPrefix = "foo/"
|
||||
|
||||
testConfig{
|
||||
gopathFiles: map[string]string{
|
||||
"foo/bar/bar.go": "package bar \n const X = 1",
|
||||
},
|
||||
}.test(t, func(t *goimportTest) {
|
||||
buf, err := Process(t.gopath+"/src/test/t.go", []byte("package main \n const Y = bar.X \n const _ = runtime.GOOS"), &Options{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const want = `package main
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"foo/bar"
|
||||
)
|
||||
|
||||
const Y = bar.X
|
||||
const _ = runtime.GOOS
|
||||
`
|
||||
if string(buf) != want {
|
||||
t.Errorf("Got:\n%s\nWant:\n%s", buf, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Tests that running goimport on files in GOROOT (for people hacking
|
||||
// on Go itself) don't cause the GOPATH to be scanned (which might be
|
||||
// much bigger).
|
||||
func TestOptimizationWhenInGoroot(t *testing.T) {
|
||||
testConfig{
|
||||
gopathFiles: map[string]string{
|
||||
"foo/foo.go": "package foo\nconst X = 1\n",
|
||||
},
|
||||
}.test(t, func(t *goimportTest) {
|
||||
testHookScanDir = func(dir string) {
|
||||
if dir != filepath.Join(build.Default.GOROOT, "src") {
|
||||
t.Errorf("unexpected dir scan of %s", dir)
|
||||
}
|
||||
}
|
||||
const in = "package foo\n\nconst Y = bar.X\n"
|
||||
buf, err := Process(t.goroot+"/src/foo/foo.go", []byte(in), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(buf) != in {
|
||||
t.Errorf("got:\n%q\nwant unchanged:\n%q\n", in, buf)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Tests that "package documentation" files are ignored.
|
||||
func TestIgnoreDocumentationPackage(t *testing.T) {
|
||||
testConfig{
|
||||
gopathFiles: map[string]string{
|
||||
"foo/foo.go": "package foo\nconst X = 1\n",
|
||||
"foo/doc.go": "package documentation \n // just to confuse things\n",
|
||||
},
|
||||
}.test(t, func(t *goimportTest) {
|
||||
const in = "package x\n\nconst Y = foo.X\n"
|
||||
const want = "package x\n\nimport \"foo\"\n\nconst Y = foo.X\n"
|
||||
buf, err := Process(t.gopath+"/src/x/x.go", []byte(in), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(buf) != want {
|
||||
t.Errorf("wrong output.\ngot:\n%q\nwant:\n%q\n", in, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Tests importPathToNameGoPathParse and in particular that it stops
|
||||
// after finding the first non-documentation package name, not
|
||||
// reporting an error on inconsistent package names (since it should
|
||||
// never make it that far).
|
||||
func TestImportPathToNameGoPathParse(t *testing.T) {
|
||||
testConfig{
|
||||
gopathFiles: map[string]string{
|
||||
"example.net/pkg/doc.go": "package documentation\n", // ignored
|
||||
"example.net/pkg/gen.go": "package main\n", // also ignored
|
||||
"example.net/pkg/pkg.go": "package the_pkg_name_to_find\n and this syntax error is ignored because of parser.PackageClauseOnly",
|
||||
"example.net/pkg/z.go": "package inconsistent\n", // inconsistent but ignored
|
||||
},
|
||||
}.test(t, func(t *goimportTest) {
|
||||
got, err := importPathToNameGoPathParse("example.net/pkg", filepath.Join(t.gopath, "src", "other.net"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const want = "the_pkg_name_to_find"
|
||||
if got != want {
|
||||
t.Errorf("importPathToNameGoPathParse(..) = %q; want %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestIgnoreConfiguration(t *testing.T) {
|
||||
testConfig{
|
||||
gopathFiles: map[string]string{
|
||||
".goimportsignore": "# comment line\n\n example.net", // tests comment, blank line, whitespace trimming
|
||||
"example.net/pkg/pkg.go": "package pkg\nconst X = 1",
|
||||
"otherwise-longer-so-worse.example.net/foo/pkg/pkg.go": "package pkg\nconst X = 1",
|
||||
},
|
||||
}.test(t, func(t *goimportTest) {
|
||||
const in = "package x\n\nconst _ = pkg.X\n"
|
||||
const want = "package x\n\nimport \"otherwise-longer-so-worse.example.net/foo/pkg\"\n\nconst _ = pkg.X\n"
|
||||
buf, err := Process(t.gopath+"/src/x/x.go", []byte(in), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(buf) != want {
|
||||
t.Errorf("wrong output.\ngot:\n%q\nwant:\n%q\n", buf, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Skip "node_modules" directory.
|
||||
func TestSkipNodeModules(t *testing.T) {
|
||||
testConfig{
|
||||
gopathFiles: map[string]string{
|
||||
"example.net/node_modules/pkg/a.go": "package pkg\nconst X = 1",
|
||||
"otherwise-longer.net/not_modules/pkg/a.go": "package pkg\nconst X = 1",
|
||||
},
|
||||
}.test(t, func(t *goimportTest) {
|
||||
const in = "package x\n\nconst _ = pkg.X\n"
|
||||
const want = "package x\n\nimport \"otherwise-longer.net/not_modules/pkg\"\n\nconst _ = pkg.X\n"
|
||||
buf, err := Process(t.gopath+"/src/x/x.go", []byte(in), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(buf) != want {
|
||||
t.Errorf("wrong output.\ngot:\n%q\nwant:\n%q\n", buf, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// golang.org/issue/16458 -- if GOROOT is a prefix of GOPATH, GOPATH is ignored.
|
||||
func TestGoRootPrefixOfGoPath(t *testing.T) {
|
||||
dir := mustTempDir(t, "importstest")
|
||||
defer os.RemoveAll(dir)
|
||||
testConfig{
|
||||
goroot: filepath.Join(dir, "go"),
|
||||
gopath: filepath.Join(dir, "gopath"),
|
||||
gopathFiles: map[string]string{
|
||||
"example.com/foo/pkg.go": "package foo\nconst X = 1",
|
||||
},
|
||||
}.test(t, func(t *goimportTest) {
|
||||
const in = "package x\n\nconst _ = foo.X\n"
|
||||
const want = "package x\n\nimport \"example.com/foo\"\n\nconst _ = foo.X\n"
|
||||
buf, err := Process(t.gopath+"/src/x/x.go", []byte(in), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(buf) != want {
|
||||
t.Errorf("wrong output.\ngot:\n%q\nwant:\n%q\n", buf, want)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func strSet(ss []string) map[string]bool {
|
||||
m := make(map[string]bool)
|
||||
for _, s := range ss {
|
||||
|
@ -1134,3 +1435,141 @@ func strSet(ss []string) map[string]bool {
|
|||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func TestPkgIsCandidate(t *testing.T) {
|
||||
tests := [...]struct {
|
||||
filename string
|
||||
pkgIdent string
|
||||
pkg *pkg
|
||||
want bool
|
||||
}{
|
||||
// normal match
|
||||
0: {
|
||||
filename: "/gopath/src/my/pkg/pkg.go",
|
||||
pkgIdent: "client",
|
||||
pkg: &pkg{
|
||||
dir: "/gopath/src/client",
|
||||
importPath: "client",
|
||||
importPathShort: "client",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
// not a match
|
||||
1: {
|
||||
filename: "/gopath/src/my/pkg/pkg.go",
|
||||
pkgIdent: "zzz",
|
||||
pkg: &pkg{
|
||||
dir: "/gopath/src/client",
|
||||
importPath: "client",
|
||||
importPathShort: "client",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
// would be a match, but "client" appears too deep.
|
||||
2: {
|
||||
filename: "/gopath/src/my/pkg/pkg.go",
|
||||
pkgIdent: "client",
|
||||
pkg: &pkg{
|
||||
dir: "/gopath/src/client/foo/foo/foo",
|
||||
importPath: "client/foo/foo",
|
||||
importPathShort: "client/foo/foo",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
// not an exact match, but substring is good enough.
|
||||
3: {
|
||||
filename: "/gopath/src/my/pkg/pkg.go",
|
||||
pkgIdent: "client",
|
||||
pkg: &pkg{
|
||||
dir: "/gopath/src/foo/go-client",
|
||||
importPath: "foo/go-client",
|
||||
importPathShort: "foo/go-client",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
// "internal" package, and not visible
|
||||
4: {
|
||||
filename: "/gopath/src/my/pkg/pkg.go",
|
||||
pkgIdent: "client",
|
||||
pkg: &pkg{
|
||||
dir: "/gopath/src/foo/internal/client",
|
||||
importPath: "foo/internal/client",
|
||||
importPathShort: "foo/internal/client",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
// "internal" package but visible
|
||||
5: {
|
||||
filename: "/gopath/src/foo/bar.go",
|
||||
pkgIdent: "client",
|
||||
pkg: &pkg{
|
||||
dir: "/gopath/src/foo/internal/client",
|
||||
importPath: "foo/internal/client",
|
||||
importPathShort: "foo/internal/client",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
// "vendor" package not visible
|
||||
6: {
|
||||
filename: "/gopath/src/foo/bar.go",
|
||||
pkgIdent: "client",
|
||||
pkg: &pkg{
|
||||
dir: "/gopath/src/other/vendor/client",
|
||||
importPath: "other/vendor/client",
|
||||
importPathShort: "client",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
// "vendor" package, visible
|
||||
7: {
|
||||
filename: "/gopath/src/foo/bar.go",
|
||||
pkgIdent: "client",
|
||||
pkg: &pkg{
|
||||
dir: "/gopath/src/foo/vendor/client",
|
||||
importPath: "other/foo/client",
|
||||
importPathShort: "client",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
// Ignore hyphens.
|
||||
8: {
|
||||
filename: "/gopath/src/foo/bar.go",
|
||||
pkgIdent: "socketio",
|
||||
pkg: &pkg{
|
||||
dir: "/gopath/src/foo/socket-io",
|
||||
importPath: "foo/socket-io",
|
||||
importPathShort: "foo/socket-io",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
// Ignore case.
|
||||
9: {
|
||||
filename: "/gopath/src/foo/bar.go",
|
||||
pkgIdent: "fooprod",
|
||||
pkg: &pkg{
|
||||
dir: "/gopath/src/foo/FooPROD",
|
||||
importPath: "foo/FooPROD",
|
||||
importPathShort: "foo/FooPROD",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
// Ignoring both hyphens and case together.
|
||||
10: {
|
||||
filename: "/gopath/src/foo/bar.go",
|
||||
pkgIdent: "fooprod",
|
||||
pkg: &pkg{
|
||||
dir: "/gopath/src/foo/Foo-PROD",
|
||||
importPath: "foo/Foo-PROD",
|
||||
importPathShort: "foo/Foo-PROD",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
got := pkgIsCandidate(tt.filename, tt.pkgIdent, tt.pkg)
|
||||
if got != tt.want {
|
||||
t.Errorf("test %d. pkgIsCandidate(%q, %q, %+v) = %v; want %v",
|
||||
i, tt.filename, tt.pkgIdent, *tt.pkg, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
// Package oracle contains the implementation of the oracle tool whose
|
||||
// command-line is provided by golang.org/x/tools/cmd/oracle.
|
||||
//
|
||||
// http://golang.org/s/oracle-design
|
||||
// http://golang.org/s/oracle-user-manual
|
||||
// DEPRECATED: oracle has been superseded by guru;
|
||||
// see https://golang.org/s/using-guru for details.
|
||||
// This package will be deleted on October 1, 2016.
|
||||
//
|
||||
package oracle // import "golang.org/x/tools/oracle"
|
||||
|
||||
|
|
|
@ -228,6 +228,12 @@ func TestOracle(t *testing.T) {
|
|||
"testdata/src/referrers-json/main.go",
|
||||
"testdata/src/what-json/main.go",
|
||||
} {
|
||||
if filename == "testdata/src/referrers/main.go" && runtime.GOOS == "plan9" {
|
||||
// Disable this test on plan9 since it expects a particular
|
||||
// wording for a "no such file or directory" error.
|
||||
continue
|
||||
}
|
||||
|
||||
useJson := strings.Contains(filename, "-json/")
|
||||
queries := parseQueries(t, filename)
|
||||
golden := filename + "lden"
|
||||
|
|
|
@ -61,7 +61,7 @@ func Move(ctxt *build.Context, from, to, moveTmpl string) error {
|
|||
}
|
||||
|
||||
// Build the import graph and figure out which packages to update.
|
||||
fwd, rev, errors := importgraph.Build(ctxt)
|
||||
_, rev, errors := importgraph.Build(ctxt)
|
||||
if len(errors) > 0 {
|
||||
// With a large GOPATH tree, errors are inevitable.
|
||||
// Report them but proceed.
|
||||
|
@ -74,14 +74,17 @@ func Move(ctxt *build.Context, from, to, moveTmpl string) error {
|
|||
// Determine the affected packages---the set of packages whose import
|
||||
// statements need updating.
|
||||
affectedPackages := map[string]bool{from: true}
|
||||
destinations := map[string]string{} // maps old dir to new dir
|
||||
destinations := make(map[string]string) // maps old import path to new import path
|
||||
for pkg := range subpackages(ctxt, srcDir, from) {
|
||||
for r := range rev[pkg] {
|
||||
affectedPackages[r] = true
|
||||
}
|
||||
destinations[pkg] = strings.Replace(pkg,
|
||||
// Ensure directories have a trailing "/".
|
||||
filepath.Join(from, ""), filepath.Join(to, ""), 1)
|
||||
// Ensure directories have a trailing separator.
|
||||
dest := strings.Replace(pkg,
|
||||
filepath.Join(from, ""),
|
||||
filepath.Join(to, ""),
|
||||
1)
|
||||
destinations[pkg] = filepath.ToSlash(dest)
|
||||
}
|
||||
|
||||
// Load all the affected packages.
|
||||
|
@ -100,7 +103,6 @@ func Move(ctxt *build.Context, from, to, moveTmpl string) error {
|
|||
|
||||
m := mover{
|
||||
ctxt: ctxt,
|
||||
fwd: fwd,
|
||||
rev: rev,
|
||||
iprog: iprog,
|
||||
from: from,
|
||||
|
@ -169,8 +171,8 @@ type mover struct {
|
|||
// with new package names or import paths.
|
||||
iprog *loader.Program
|
||||
ctxt *build.Context
|
||||
// fwd and rev are the forward and reverse import graphs
|
||||
fwd, rev importgraph.Graph
|
||||
// rev is the reverse import graph.
|
||||
rev importgraph.Graph
|
||||
// from and to are the source and destination import
|
||||
// paths. fromDir and toDir are the source and destination
|
||||
// absolute paths that package source files will be moved between.
|
||||
|
|
|
@ -7,9 +7,12 @@ package rename
|
|||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -111,10 +114,14 @@ var _ foo.T
|
|||
}
|
||||
|
||||
func TestMoves(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("broken on Windows; see golang.org/issue/16384")
|
||||
}
|
||||
tests := []struct {
|
||||
ctxt *build.Context
|
||||
from, to string
|
||||
want map[string]string
|
||||
wantWarnings []string
|
||||
}{
|
||||
// Simple example.
|
||||
{
|
||||
|
@ -267,6 +274,77 @@ var _ bar.T
|
|||
/* import " this is not an import comment */
|
||||
`},
|
||||
},
|
||||
// Import name conflict generates a warning, not an error.
|
||||
{
|
||||
ctxt: fakeContext(map[string][]string{
|
||||
"x": {},
|
||||
"a": {`package a; type A int`},
|
||||
"b": {`package b; type B int`},
|
||||
"conflict": {`package conflict
|
||||
|
||||
import "a"
|
||||
import "b"
|
||||
var _ a.A
|
||||
var _ b.B
|
||||
`},
|
||||
"ok": {`package ok
|
||||
import "b"
|
||||
var _ b.B
|
||||
`},
|
||||
}),
|
||||
from: "b", to: "x/a",
|
||||
want: map[string]string{
|
||||
"/go/src/a/0.go": `package a; type A int`,
|
||||
"/go/src/ok/0.go": `package ok
|
||||
|
||||
import "x/a"
|
||||
|
||||
var _ a.B
|
||||
`,
|
||||
"/go/src/conflict/0.go": `package conflict
|
||||
|
||||
import "a"
|
||||
import "x/a"
|
||||
|
||||
var _ a.A
|
||||
var _ b.B
|
||||
`,
|
||||
"/go/src/x/a/0.go": `package a
|
||||
|
||||
type B int
|
||||
`,
|
||||
},
|
||||
wantWarnings: []string{
|
||||
`/go/src/conflict/0.go:4:8: renaming this imported package name "b" to "a"`,
|
||||
`/go/src/conflict/0.go:3:8: conflicts with imported package name in same block`,
|
||||
`/go/src/conflict/0.go:3:8: skipping update of this file`,
|
||||
},
|
||||
},
|
||||
// Rename with same base name.
|
||||
{
|
||||
ctxt: fakeContext(map[string][]string{
|
||||
"x": {},
|
||||
"y": {},
|
||||
"x/foo": {`package foo
|
||||
|
||||
type T int
|
||||
`},
|
||||
"main": {`package main; import "x/foo"; var _ foo.T`},
|
||||
}),
|
||||
from: "x/foo", to: "y/foo",
|
||||
want: map[string]string{
|
||||
"/go/src/y/foo/0.go": `package foo
|
||||
|
||||
type T int
|
||||
`,
|
||||
"/go/src/main/0.go": `package main
|
||||
|
||||
import "y/foo"
|
||||
|
||||
var _ foo.T
|
||||
`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
@ -296,6 +374,16 @@ var _ bar.T
|
|||
}
|
||||
got[path] = string(bytes)
|
||||
})
|
||||
var warnings []string
|
||||
reportError = func(posn token.Position, message string) {
|
||||
warning := fmt.Sprintf("%s:%d:%d: %s",
|
||||
filepath.ToSlash(posn.Filename), // for MS Windows
|
||||
posn.Line,
|
||||
posn.Column,
|
||||
message)
|
||||
warnings = append(warnings, warning)
|
||||
|
||||
}
|
||||
writeFile = func(filename string, content []byte) error {
|
||||
got[filename] = string(content)
|
||||
return nil
|
||||
|
@ -318,6 +406,13 @@ var _ bar.T
|
|||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(warnings, test.wantWarnings) {
|
||||
t.Errorf("%s: unexpected warnings:\n%s\nwant:\n%s",
|
||||
prefix,
|
||||
strings.Join(warnings, "\n"),
|
||||
strings.Join(test.wantWarnings, "\n"))
|
||||
}
|
||||
|
||||
for file, wantContent := range test.want {
|
||||
k := filepath.FromSlash(file)
|
||||
gotContent, ok := got[k]
|
||||
|
|
|
@ -174,11 +174,13 @@ var reportError = func(posn token.Position, message string) {
|
|||
fmt.Fprintf(os.Stderr, "%s: %s\n", posn, message)
|
||||
}
|
||||
|
||||
// importName renames imports of the package with the given path in
|
||||
// the given package. If fromName is not empty, only imports as
|
||||
// fromName will be renamed. If the renaming would lead to a conflict,
|
||||
// the file is left unchanged.
|
||||
// importName renames imports of fromPath within the package specified by info.
|
||||
// If fromName is not empty, importName renames only imports as fromName.
|
||||
// If the renaming would lead to a conflict, the file is left unchanged.
|
||||
func importName(iprog *loader.Program, info *loader.PackageInfo, fromPath, fromName, to string) error {
|
||||
if fromName == to {
|
||||
return nil // no-op (e.g. rename x/foo to y/foo)
|
||||
}
|
||||
for _, f := range info.Files {
|
||||
var from types.Object
|
||||
for _, imp := range f.Imports {
|
||||
|
@ -203,6 +205,8 @@ func importName(iprog *loader.Program, info *loader.PackageInfo, fromPath, fromN
|
|||
}
|
||||
r.check(from)
|
||||
if r.hadConflicts {
|
||||
reportError(iprog.Fset.Position(f.Imports[0].Pos()),
|
||||
"skipping update of this file")
|
||||
continue // ignore errors; leave the existing name
|
||||
}
|
||||
if err := r.update(); err != nil {
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2013-2016 Guy Bedford, Luke Hoban, Addy Osmani
|
||||
|
||||
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.
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,55 @@
|
|||
Apache License
|
||||
|
||||
Version 2.0, January 2004
|
||||
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
|
||||
You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
|
||||
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2015 The Polymer Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue