]> Cypherpunks repositories - gostls13.git/commitdiff
gotype: commandline tool to typecheck go programs
authorRobert Griesemer <gri@golang.org>
Fri, 11 Mar 2011 18:27:25 +0000 (10:27 -0800)
committerRobert Griesemer <gri@golang.org>
Fri, 11 Mar 2011 18:27:25 +0000 (10:27 -0800)
First version. Handles scope analysis only at the
moment.

R=rsc, r, eds
CC=golang-dev
https://golang.org/cl/4259065

src/cmd/Makefile
src/cmd/gotype/Makefile [new file with mode: 0644]
src/cmd/gotype/doc.go [new file with mode: 0644]
src/cmd/gotype/gotype.go [new file with mode: 0644]
src/pkg/Makefile

index 104e9f5df3406df4763a33a197f1876fcd379e3b..779bd44c7932a14a1b7883b6d0bd6de18121bb25 100644 (file)
@@ -43,6 +43,7 @@ CLEANDIRS=\
        godoc\
        gofmt\
        goinstall\
+       gotype\
        goyacc\
        hgpatch\
 
diff --git a/src/cmd/gotype/Makefile b/src/cmd/gotype/Makefile
new file mode 100644 (file)
index 0000000..ac9e3be
--- /dev/null
@@ -0,0 +1,11 @@
+# Copyright 2011 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+include ../../Make.inc
+
+TARG=gotype
+GOFILES=\
+       gotype.go\
+
+include ../../Make.cmd
diff --git a/src/cmd/gotype/doc.go b/src/cmd/gotype/doc.go
new file mode 100644 (file)
index 0000000..1bd4b5f
--- /dev/null
@@ -0,0 +1,57 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+The gotype command does syntactic and semantic analysis of Go files
+and packages similar to the analysis performed by the front-end of
+a Go compiler. Errors are reported if the analysis fails; otherwise
+gotype is quiet (unless -v is set).
+
+Without a list of paths, gotype processes the standard input, which must
+be the source of a single package file.
+
+Given a list of file names, each file must be a source file belonging to
+the same package unless the package name is explicitly specified with the
+-p flag.
+
+Given a directory name, gotype collects all .go files in the directory
+and processes them as if they were provided as an explicit list of file
+names. Each directory is processed independently. Files starting with .
+or not ending in .go are ignored.
+
+Usage:
+       gotype [flags] [path ...]
+
+The flags are:
+       -p pkgName
+               process only those files in package pkgName.
+       -r
+               recursively process subdirectories.
+       -v
+               verbose mode.
+
+Debugging flags:
+       -trace
+               print parse trace (disables concurrent parsing).
+       -ast
+               print AST (disables concurrent parsing).
+
+
+Examples
+
+To check the files file.go, old.saved, and .ignored:
+
+       gotype file.go old.saved .ignored
+
+To check all .go files belonging to package main in the current directory
+and recursively in all subdirectories:
+
+       gotype -p main -r .
+
+To verify the output of a pipe:
+
+       echo "package foo" | gotype
+
+*/
+package documentation
diff --git a/src/cmd/gotype/gotype.go b/src/cmd/gotype/gotype.go
new file mode 100644 (file)
index 0000000..435f1aa
--- /dev/null
@@ -0,0 +1,191 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+       "flag"
+       "fmt"
+       "go/ast"
+       "go/parser"
+       "go/scanner"
+       "go/token"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+       "strings"
+)
+
+
+var (
+       // main operation modes
+       pkgName   = flag.String("p", "", "process only those files in package pkgName")
+       recursive = flag.Bool("r", false, "recursively process subdirectories")
+       verbose   = flag.Bool("v", false, "verbose mode")
+
+       // debugging support
+       printTrace = flag.Bool("trace", false, "print parse trace")
+       printAST   = flag.Bool("ast", false, "print AST")
+)
+
+
+var (
+       fset       = token.NewFileSet()
+       exitCode   = 0
+       parserMode = parser.DeclarationErrors
+)
+
+
+func usage() {
+       fmt.Fprintf(os.Stderr, "usage: gotype [flags] [path ...]\n")
+       flag.PrintDefaults()
+       os.Exit(2)
+}
+
+
+func processFlags() {
+       flag.Usage = usage
+       flag.Parse()
+       if *printTrace {
+               parserMode |= parser.Trace
+       }
+}
+
+
+func report(err os.Error) {
+       scanner.PrintError(os.Stderr, err)
+       exitCode = 2
+}
+
+
+// parseFile returns the AST for the given file.
+// The result
+func parseFile(filename string) *ast.File {
+       if *verbose {
+               fmt.Println(filename)
+       }
+
+       // get source
+       src, err := ioutil.ReadFile(filename)
+       if err != nil {
+               report(err)
+               return nil
+       }
+
+       // ignore files with different package name
+       if *pkgName != "" {
+               file, err := parser.ParseFile(fset, filename, src, parser.PackageClauseOnly)
+               if err != nil {
+                       report(err)
+                       return nil
+               }
+               if file.Name.Name != *pkgName {
+                       if *verbose {
+                               fmt.Printf("\tignored (package %s)\n", file.Name.Name)
+                       }
+                       return nil
+               }
+       }
+
+       // parse entire file
+       file, err := parser.ParseFile(fset, filename, src, parserMode)
+       if err != nil {
+               report(err)
+               return nil
+       }
+       if *printAST {
+               ast.Print(fset, file)
+       }
+
+       return file
+}
+
+
+// BUG(gri): At the moment, only single-file scope analysis is performed.
+
+func processPackage(filenames []string) {
+       var files []*ast.File
+       pkgName := ""
+       for _, filename := range filenames {
+               file := parseFile(filename)
+               if file == nil {
+                       continue // ignore file
+               }
+               // package names must match
+               // TODO(gri): this check should be moved into a
+               //            function making the package below
+               if pkgName == "" {
+                       // first package file
+                       pkgName = file.Name.Name
+               } else {
+                       if file.Name.Name != pkgName {
+                               report(os.NewError(fmt.Sprintf("file %q is in package %q not %q", filename, file.Name.Name, pkgName)))
+                               continue
+                       }
+               }
+               files = append(files, file)
+       }
+
+       // TODO(gri): make a ast.Package and analyze it
+       _ = files
+}
+
+
+func isGoFilename(filename string) bool {
+       // ignore non-Go files
+       return !strings.HasPrefix(filename, ".") && strings.HasSuffix(filename, ".go")
+}
+
+
+func processDirectory(dirname string) {
+       f, err := os.Open(dirname, os.O_RDONLY, 0)
+       if err != nil {
+               report(err)
+               return
+       }
+       filenames, err := f.Readdirnames(-1)
+       f.Close()
+       if err != nil {
+               report(err)
+               // continue since filenames may not be empty
+       }
+       for i, filename := range filenames {
+               filenames[i] = filepath.Join(dirname, filename)
+       }
+       processFiles(filenames, false)
+}
+
+
+func processFiles(filenames []string, allFiles bool) {
+       i := 0
+       for _, filename := range filenames {
+               switch info, err := os.Stat(filename); {
+               case err != nil:
+                       report(err)
+               case info.IsRegular():
+                       if allFiles || isGoFilename(info.Name) {
+                               filenames[i] = filename
+                               i++
+                       }
+               case info.IsDirectory():
+                       if allFiles || *recursive {
+                               processDirectory(filename)
+                       }
+               }
+       }
+       processPackage(filenames[0:i])
+}
+
+
+func main() {
+       processFlags()
+
+       if flag.NArg() == 0 {
+               processPackage([]string{os.Stdin.Name()})
+       } else {
+               processFiles(flag.Args(), true)
+       }
+
+       os.Exit(exitCode)
+}
index 31d7e1a68214b7b087d05a62e97b8c48ab66e2a3..ccba63b6aa8c3539021be10516d3255cf5ba08ab 100644 (file)
@@ -156,6 +156,7 @@ DIRS=\
        ../cmd/ebnflint\
        ../cmd/godoc\
        ../cmd/gofmt\
+       ../cmd/gotype\
        ../cmd/goinstall\
        ../cmd/govet\
        ../cmd/goyacc\
@@ -191,6 +192,7 @@ NOTEST=\
        ../cmd/ebnflint\
        ../cmd/godoc\
        ../cmd/gofmt\
+       ../cmd/gotype\
        ../cmd/govet\
        ../cmd/goyacc\
        ../cmd/hgpatch\