[release-branch.go1.7] all: merge master into release-branch.go1.7

72064bd cmd/heapview: dowgrade to customelements v0
337c012 x/tools/cmd/heapview: add a heading to the page
7ef02fd cmd/heapview: add karma+jasmine TS unit testing config
bf0c35b tip/doc: update doc to match latest gcloud syntax
9e74590 x/tools/cmd/heapview: break out init code
053ddb9 x/tools/cmd/heapview: add basic client serving
1c6f639 imports: don't ignore GOPATH if GOROOT is a prefix of GOPATH
ed69e84 cmd/goimports, imports: add -local flag
f328430 cmd/goimports: permit complete filename for -srcdir
5b59ce8 cmd/bundle: More idiomatic flag.Usage.
682b241 imports: make filepath.Rel work on windows
55296b3 x/tools/cmd/heapview: rename heapdump to heapview
1634796 go/ssa: fix stale docs for CreateProgram and Build
5dbb806 x/tools/cmd/heapdump: add empty heapdump command
d4a8e58 imports: skip "node_modules" directories
edf8e6f cmd/goimports, imports: optimize directory scanning and other things
caebc7a imports: ignore case and hyphens when lexically filtering candidate dirs
b825d28 cmd/oracle: announce planned deletion in 2.5 months
f2932db cmd/guru: suppress failing test on plan9
29481a3 imports: skip test on plan9
681db09 godoc/static: use window scope for checking notesEnabled
6d32be8 imports: minor fixes
c550f66 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 directories
7c26c99 imports: do less I/O finding the package name of an import
ef6b6eb cmd/guru: what: include imported package names in sameids
92480e4 cmd/guru: update Emacs installation documentation
9c3986d refactor/rename: fix two bugs related to MS Windows' path separator
e047ae7 cmd/goimports, imports: make goimports great again
b3887f7 refactor/rename: fix spurious conflict report when renaming x/foo to y/foo
0835c73 imports: special case rand.Read, prevent math/rand by chance
cda5280 cmd/guru: fix quoting bug in Emacs binding
130914b tip: update package doc to refer to stable code
35c6e68 cmd/guru: update link to documentation
2b32496 cmd/guru: add menu to Emacs

Change-Id: I8d32f370464fa7af0dfcfefd8cd2f2650c5758d4
This commit is contained in:
Chris Broadfoot 2016-08-15 13:24:47 -07:00
commit 26c35b4dcf
51 changed files with 64056 additions and 414 deletions

View File

@ -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 != "" {

View File

@ -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!
*/

View File

@ -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"
@ -22,10 +27,16 @@ import (
var (
// main operation modes
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`")
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`. 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,9 +109,30 @@ func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error
target := filename
if *srcdir != "" {
// Pretend that file is from *srcdir in order to decide
// visible imports correctly.
target = filepath.Join(*srcdir, filepath.Base(filename))
// 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)
@ -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()
}

View File

@ -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
;; $ 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

View File

@ -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"

View File

@ -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):

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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"
]
}

View File

@ -117,15 +117,35 @@ 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 {
object = id.Obj.Name
decl := qpos.path[len(qpos.path)-1]
ast.Inspect(decl, func(n ast.Node) bool {
if n, ok := n.(*ast.Ident); ok && n.Obj == id.Obj {
sameids = append(sameids, n.Pos())
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
}
return true
})
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 {
if n, ok := n.(*ast.Ident); ok && n.Obj == id.Obj {
sameids = append(sameids, n.Pos())
}
return true
})
}
}
q.Output(qpos.fset, &whatResult{

View File

@ -0,0 +1 @@
BasedOnStyle: Google

1
cmd/heapview/client/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/node_modules/

View File

@ -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.

View File

@ -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 = '&#9776'; // 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"));
}

View File

@ -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();
});
});

View File

@ -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 ."
}
}

View File

@ -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'
],
})
}

View File

@ -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);

View File

@ -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
}
}

View File

@ -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"
]
}
}

79
cmd/heapview/main.go Normal file
View File

@ -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()
}

View File

@ -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"

View File

@ -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.

View File

@ -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

View File

@ -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))
}
}

View File

@ -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:

View File

@ -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)
}
}

11
go/gcimporter15/testdata/issue15920.go vendored Normal file
View File

@ -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 }

13
go/gcimporter15/testdata/p.go vendored Normal file
View File

@ -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() {}

View File

@ -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

View File

@ -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.

172
imports/fastwalk.go Normal file
View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

171
imports/fastwalk_test.go Normal file
View File

@ -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)
}
}
}

122
imports/fastwalk_unix.go Normal file
View File

@ -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
}

View File

@ -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 {
return importPathToNameBasic(importPath, srcDir)
// 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 {
return false
// 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"
}
log.Printf("scanning " + which)
defer log.Printf("scanned " + which)
}
children, err := pkgDir.Readdir(-1)
pkgDir.Close()
if err != nil {
return
dirScanMu.Lock()
if dirScan == nil {
dirScan = make(map[string]*pkg)
}
// 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.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
}
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 hasGo {
shortName := importPathToName(importpath, "")
pkgIndex.Lock()
pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{
importpath: importpath,
dir: dir,
})
pkgIndex.Unlock()
}
}
// loadExports returns a list exports for a package.
var loadExports = loadExportsGoPath
func loadExportsGoPath(dir string) map[string]bool {
exports := make(map[string]bool)
buildPkg, err := build.ImportDir(dir, 0)
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
}
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)
continue
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 !strings.HasSuffix(path, ".go") {
return nil
}
dirScanMu.Lock()
if _, dup := dirScan[dir]; !dup {
importpath := filepath.ToSlash(dir[len(srcDir)+len("/"):])
dirScan[dir] = &pkg{
importPath: importpath,
importPathShort: vendorlessImportPath(importpath),
dir: dir,
}
}
dirScanMu.Unlock()
return nil
}
for name := range f.Scope.Objects {
if ast.IsExported(name) {
exports[name] = true
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 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(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)
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 {
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 _, 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
}
wg.Add(1)
go func(importpath, dir string) {
defer wg.Done()
exports := loadExports(dir)
if exports == nil {
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)
}
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.
for symbol := range symbols {
if !exports[symbol] {
return
pkg := pkg
resc := rescv[i]
go func() {
if inTests {
testMu.RLock()
defer testMu.RUnlock()
}
}
defer func() { <-loadExportsSem }()
exports := loadExports(pkgName, pkg.dir)
// 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/"):]
}
// 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
}
mu.Unlock()
}(pkg.importpath, pkg.dir)
// If it doesn't have the right
// symbols, send nil to mean no match.
for symbol := range symbols {
if !exports[symbol] {
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
}
pkgIndex.Unlock()
wg.Wait()
return shortest, false, 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
}
}
return false
}
func hasHyphenOrUpperASCII(s string) bool {
for i := 0; i < len(s); i++ {
b := s[i]
if b == '-' || ('A' <= b && b <= 'Z') {
return true
}
}
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] == '\\'
}

View File

@ -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,22 +859,17 @@ func TestImportSymlinks(t *testing.T) {
t.Fatal(err)
}
pkgIndexOnce = &sync.Once{}
oldGOPATH := build.Default.GOPATH
build.Default.GOPATH = newGoPath
defer func() {
build.Default.GOPATH = oldGOPATH
visitedSymlinks.m = nil
}()
withEmptyGoPath(func() {
build.Default.GOPATH = newGoPath
input := `package p
input := `package p
var (
_ = fmt.Print
_ = mypkg.Foo
)
`
output := `package p
output := `package p
import (
"fmt"
@ -884,13 +881,14 @@ var (
_ = mypkg.Foo
)
`
buf, err := Process(newGoPath+"/src/myotherpkg/toformat.go", []byte(input), &Options{})
if err != nil {
t.Fatal(err)
}
if got := string(buf); got != output {
t.Fatalf("results differ\nGOT:\n%s\nWANT:\n%s\n", got, output)
}
buf, err := Process(newGoPath+"/src/myotherpkg/toformat.go", []byte(input), &Options{})
if err != nil {
t.Fatal(err)
}
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,30 +900,12 @@ 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)
}
input := `package p
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 (
"fmt"
@ -938,13 +918,14 @@ var (
_ = mypkg.Foo
)
`
buf, err := Process(newGoPath+"/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)
}
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,154 +935,198 @@ func TestFindImportGoPath(t *testing.T) {
}
defer os.RemoveAll(goroot)
pkgIndexOnce = &sync.Once{}
origStdlib := stdlib
defer func() {
stdlib = origStdlib
}()
stdlib = nil
// Test against imaginary bits/bytes package in std lib
bytesDir := filepath.Join(goroot, "src", "pkg", "bits", "bytes")
for _, tag := range build.Default.ReleaseTags {
// Go 1.4 rearranged the GOROOT tree to remove the "pkg" path component.
if tag == "go1.4" {
bytesDir = filepath.Join(goroot, "src", "bits", "bytes")
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 {
// Go 1.4 rearranged the GOROOT tree to remove the "pkg" path component.
if tag == "go1.4" {
bytesDir = filepath.Join(goroot, "src", "bits", "bytes")
}
}
}
if err := os.MkdirAll(bytesDir, 0755); err != nil {
t.Fatal(err)
}
bytesSrcPath := filepath.Join(bytesDir, "bytes.go")
bytesPkgPath := "bits/bytes"
bytesSrc := []byte(`package bytes
if err := os.MkdirAll(bytesDir, 0755); err != nil {
t.Fatal(err)
}
bytesSrcPath := filepath.Join(bytesDir, "bytes.go")
bytesPkgPath := "bits/bytes"
bytesSrc := []byte(`package bytes
type Buffer2 struct {}
`)
if err := ioutil.WriteFile(bytesSrcPath, bytesSrc, 0775); err != nil {
t.Fatal(err)
}
oldGOROOT := build.Default.GOROOT
if err := ioutil.WriteFile(bytesSrcPath, bytesSrc, 0775); err != nil {
t.Fatal(err)
}
build.Default.GOROOT = goroot
got, rename, err := findImportGoPath("bytes", map[string]bool{"Buffer2": true}, "x.go")
if err != nil {
t.Fatal(err)
}
if got != bytesPkgPath || rename {
t.Errorf(`findImportGoPath("bytes", Buffer2 ...)=%q, %t, want "%s", false`, got, rename, bytesPkgPath)
}
got, rename, err = findImportGoPath("bytes", map[string]bool{"Missing": true}, "x.go")
if err != nil {
t.Fatal(err)
}
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
build.Default.GOROOT = goroot
oldGOROOT := build.Default.GOROOT
build.Default.GOPATH = ""
visitedSymlinks.m = nil
testHookScanDir = func(string) {}
testMu.Unlock()
defer func() {
build.Default.GOROOT = oldGOROOT
testMu.Lock()
testHookScanDir = func(string) {}
build.Default.GOPATH = oldGOPATH
build.Default.GOROOT = oldGOROOT
testMu.Unlock()
}()
got, rename, err := findImportGoPath("bytes", map[string]bool{"Buffer2": true}, "x.go")
if err != nil {
t.Fatal(err)
}
if got != bytesPkgPath || rename {
t.Errorf(`findImportGoPath("bytes", Buffer2 ...)=%q, %t, want "%s", false`, got, rename, bytesPkgPath)
}
got, rename, err = findImportGoPath("bytes", map[string]bool{"Missing": true}, "x.go")
if err != nil {
t.Fatal(err)
}
if got != "" || rename {
t.Errorf(`findImportGoPath("bytes", Missing ...)=%q, %t, want "", false`, got, rename)
}
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).
_, err := os.Stat(filepath.Join(runtime.GOROOT(), "src/internal/race"))
if err != nil {
t.Skip(err)
}
// 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).
_, err := os.Stat(filepath.Join(runtime.GOROOT(), "src/internal/race"))
if err != nil {
t.Skip(err)
}
got, rename, err := findImportGoPath("race", map[string]bool{"Acquire": true}, filepath.Join(runtime.GOROOT(), "src/math/x.go"))
if err != nil {
t.Fatal(err)
}
if got != "internal/race" || rename {
t.Errorf(`findImportGoPath("race", Acquire ...) = %q, %t; want "internal/race", false`, got, rename)
}
got, rename, err := findImportGoPath("race", map[string]bool{"Acquire": true}, filepath.Join(runtime.GOROOT(), "src/math/x.go"))
if err != nil {
t.Fatal(err)
}
if got != "internal/race" || 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
got, rename, err = findImportGoPath("race", map[string]bool{"Acquire": true}, filepath.Join(runtime.GOROOT(), "x.go"))
if err != nil {
t.Fatal(err)
}
if got != "" || rename {
t.Errorf(`findImportGoPath("race", Acquire ...)=%q, %t, want "", false`, got, rename)
}
})
}
// should not be able to use internal from outside that tree
got, rename, err = findImportGoPath("race", map[string]bool{"Acquire": true}, filepath.Join(runtime.GOROOT(), "x.go"))
if err != nil {
t.Fatal(err)
}
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"))
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)
}
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"
if got != want || 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)
}
_, err := os.Stat(filepath.Join(runtime.GOROOT(), "src/vendor"))
if err != nil {
t.Skip(err)
}
target := filepath.Join(runtime.GOROOT(), "src/math/x.go")
out, err := Process(target, []byte("package http\nimport \"bytes\"\nfunc f() { strings.NewReader(); hpack.HuffmanDecode() }\n"), nil)
target := filepath.Join(runtime.GOROOT(), "src/math/x.go")
out, err := Process(target, []byte("package http\nimport \"bytes\"\nfunc f() { strings.NewReader(); hpack.HuffmanDecode() }\n"), nil)
if err != nil {
t.Fatal(err)
}
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)
}
}
}

View File

@ -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"

View File

@ -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"

View File

@ -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.

View File

@ -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
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]

View File

@ -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 {

22
third_party/moduleloader/LICENSE vendored Normal file
View File

@ -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

55
third_party/typescript/LICENSE vendored Normal file
View File

@ -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

60312
third_party/typescript/typescript.js vendored Normal file

File diff suppressed because one or more lines are too long

27
third_party/webcomponents/LICENSE vendored Normal file
View File

@ -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