stan

package module
v0.0.0-...-2ab8d15 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 19, 2018 License: BSD-3-Clause Imports: 20 Imported by: 0

README

stan

Short for STatic ANalysis, stan's goal is to make it easier to write custom static analysis tests for your golang project.

In addition to *ast.Package, *types.Info and *types.Package, stan provides a higher level API to make it easier when your objective relates to particular objects/types.

stan's API and feature set are not stable.

Standard walk-the-AST approach

// don't compare time.Time values with ==

for _, pkg := range stan.Pkgs("your/namespace/...") {
  timeDotTime := pkg.LookupType("time.Time")

  stan.WalkAST(pkg.Node, func(n ast.Node, ancs stan.Ancestors) {
    binary, _ := n.(*ast.BinaryExpr)
    if binary == nil {
      return
    }

    if binary.Op != token.EQL && binary.Op != token.NEQ {
      return
    }

    if types.Identical(pkg.TypeOf(binary.X), timeDotTime) {
      t.Errorf("Use Equal() to compare time.Time values instead of == at %s", pkg.Pos(binary))
    }
  })
}

Object based approach


// find *csv.Writer users that call Flush() but never check Error()

for _, pkg := range stan.Pkgs("your/namespace/...") {
  naughtyWriters := make(map[types.Object]bool)

  csvWriterFlush := pkg.LookupObject("encoding/csv.Writer.Flush")
  for _, inv := range pkg.InvocationsOf(csvWriterFlush) {
    naughtyWriters[inv.Invocant] = true
  }

  csvWriterError := pkg.LookupObject("encoding/csv.Writer.Error")
  for _, inv := range pkg.InvocationsOf(csvWriterError) {
    delete(naughtyWriters, inv.Invocant)
  }

  for naughty := range naughtyWriters {
    t.Errorf("*csv.Writer calls Flush() but not Error() at %s", pkg.Pos(naughty))
  }
}

Test your static tests


func checkUseTimeEqual(pkg *stan.Package) []error {
  // above test to catch code comparing time.Time with ==
}

func TestUseTimeEqual(t *testing.T) {
  // invoke static check on your code
  for _, pkg := range stan.Pkgs("your/namespace/...") {
    for _, err := range checkUseTimeEqual(pkg) {
      t.Error(err)
    }
  }

  // unit test your static test
  pkg := stan.EvalPkg(`
package fake

import "time"

func foo() {
  now := time.Now()
  if now != now.Round(0) {
    panic("oops!")
  }
}
`)

  errs := checkUseTimeEqual(pkg)

  if len(errs) != 1 {
    t.Error("expected an error")
  }

  pkg = stan.EvalPkg(`
package fake

import "time"

func foo() {
  now := time.Now()
  if !now.Equal(now.Round(0)) {
    panic("oops!")
  }
}
`)

  errs = checkUseTimeEqual(pkg)

  if len(errs) != 0 {
    t.Error("expected no errors")
  }
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NextStmt

func NextStmt(n ast.Node, ancs Ancestors) ast.Stmt

Returns the "next" statement after a given node. It searches through ancestors until it finds the first BlockStmt, then it returns the next statement in the block relative to the statement of which n is a descendant.

func WalkAST

func WalkAST(n ast.Node, fn func(node ast.Node, ancs Ancestors))

Walk the AST starting rooted at n yielding the slice of ancestors for each node visited. The ancestors slice is mutated during traversal so be sure to copy() the ancestors if you want to save them off.

Types

type Ancestors

type Ancestors []ast.Node

Ancestors is a slice of ast.Nodes representing a node's ancestor nodes in the AST. A node's direct parent is the final node in the Ancestors.

func (Ancestors) Peek

func (a Ancestors) Peek() ast.Node

Peek() returns the closest ancestor in a, or nil if a is empty.

func (*Ancestors) Pop

func (a *Ancestors) Pop() ast.Node

Pop() removes and returns the closest ancestor in a. Pop() returns nil if a is empty.

type Invocation

type Invocation struct {
	// Invocant object, if available.
	Invocant types.Object
	// Args to function invocation
	Args []ast.Expr
	// Invocation's *ast.CallExpr node
	Call *ast.CallExpr
}

Invocation represents the invocation of a *types.Func.

type ObjectLifetime

type ObjectLifetime struct {
	// Lexical first and last use of object
	First, Last token.Pos
	// Definition of object
	Def *ast.Ident
	// Uses of object
	Uses []*ast.Ident
}

ObjectLifetime represents the "lifetime" of an object.

type Package

type Package struct {
	Node      *ast.Package
	Fset      *token.FileSet
	TypesInfo *types.Info
	TypesPkg  *types.Package
	// contains filtered or unexported fields
}

Package contains combines the *ast.Package and *types.Package into a single object.

func EvalPkg

func EvalPkg(code string) *Package

EvalPkg() parses and type checks code into a *Package. EvalPkg() is useful for unit testing static analysis tests. Vendor imports operate as if the code was run from os.Getwd(). EvalPkg() panics if there is an error parsing or type checking code.

func Pkgs

func Pkgs(pkgPaths ...string) []*Package

Pkgs() finds, parses and type checks the packages specified by pkgPaths. Wildcard "..." expressions may be used, similar to various "go" commands. Pkgs() panics if there is a parse error, "hard" type check error, or if no such package could be found.

In order to maximize test coverage, Pkgs() does a few potentially unexpected things to parse/check as much code as possible:

  • includes *_test.go files in packages
  • includes "XTest" _test packages as separate packages
  • attempts to invoke cgo preprocessor on cgo files so type info is available
  • loads all *.go files, even if non-buildable due to build constraints (stan will rename duplicate objects to prevent type checking errors, and ignore "hard" type check error for non-buildable files)

func (*Package) AncestorsOf

func (p *Package) AncestorsOf(target ast.Node) Ancestors

Look up ancestor nodes of given node. AncestorsOf panics if the target node is not found in p's AST. If you need the ancestors of many nodes you may be better off walking the AST once yourself.

func (*Package) DeclOf

func (p *Package) DeclOf(o types.Object) (otherPkg *Package, node ast.Node, ancs Ancestors)

Look up where a types.Object is declared. Particularly useful for jumping to the implementation of a function. otherPkg is the *Package containing the declaration, node is the innermsot ast.Node of o in the declaration (often *ast.Ident), and ancs is the ancestors of id.

func (*Package) Files

func (p *Package) Files() map[string]*ast.File

Files() returns a map of file name to *ast.File for the files that make up p.

func (*Package) InvocationsOf

func (p *Package) InvocationsOf(obj types.Object) []Invocation

InvocationsOf() returns the invocations of obj within p. InvocationsOf panics if obj is not a *types.Func.

func (*Package) IterateObjects

func (p *Package) IterateObjects(f func(types.Object))

IterateObjects() iterates over all types.Objects in p.

func (*Package) LifetimeOf

func (p *Package) LifetimeOf(obj types.Object) ObjectLifetime

LifetimeOf() returns an object representing the lifetime of types.Object obj within p. If obj is not used by p, LifetimeOf returns the zero value ObjectLifetime.

func (*Package) LookupObject

func (p *Package) LookupObject(objSpec string) types.Object

Look up a types.Object based on name.

LookupObject("io.EOF")         // yields *types.Var
LookupObject("io.Copy")        // yields *types.Func
LookupObject("io.Reader")      // yields *types.TypeName
LookupObject("io.Reader.Read") // yields *types.Func
LookupObject("io.pipe.data")   // yields *types.Var

If an error occurs or the object cannot be found, LookupObject() panics.

func (*Package) LookupType

func (p *Package) LookupType(typeSpec string) types.Type

Look up a types.Type based on the name of a type, or an unnamed type expression.

LookupType("encoding/json.Marshaler") // named types are <import path>.<name>
LookupType("*encoding/json.Encoder")  // prepend "*" to get pointer type
LookupType("[5]int")                  // for builtin types, use arbitary expression

If an error occurs or the type cannot be found, LookupType() panics.

func (*Package) ObjectOf

func (p *Package) ObjectOf(n ast.Node) types.Object

ObjectOf() returns the corresponding types.Object of an ast.Node. n normally should be an *ast.Ident, but ObjectOf will also extract the ident from an *ast.SelectorExpr. The return value can be nil if there is no corresponding object.

func (*Package) Path

func (p *Package) Path() string

Path() returns the p's unique import path (including vendor/).

func (*Package) Pos

func (p *Package) Pos(n Poser) token.Position

Pos() returns the position of an ast.Node or types.Object in the file set. Convenient for error reporting, token.Position.String() yields file:line:column.

func (*Package) String

func (p *Package) String() string

String() returns the import path of p.

func (*Package) TypeOf

func (p *Package) TypeOf(e ast.Expr) types.Type

TypeOf() returns the types.Type of a given expression. Can return nil if expresion not found.

type Poser

type Poser interface {
	Pos() token.Pos
}

Directories

Path Synopsis
internal
bar
foo

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL