Skip to content

Commit

Permalink
Treat package imports as just another type of Ref.
Browse files Browse the repository at this point in the history
Since refs have a position, this means creating a lot more Refs: each
file importing another package will have a ref to every file in the
imported package. The FromPosition is the import statement, the
FromIdentifier is either the name qualifying the import or the name of
the imported package ("." is just like any other name here). The
ToPosition is the position in each file of the package clause.
  • Loading branch information
korfuri committed Jul 9, 2017
1 parent 1b6f965 commit 5ffe092
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 46 deletions.
24 changes: 23 additions & 1 deletion dotimports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,29 @@ func TestDotImports(t *testing.T) {
pkg := pg.Packages[pkgpath]
lib := pg.Packages[pkgpath+"/lib"]
assert.Empty(t, pkg.InRefs)
assert.Len(t, pkg.OutRefs, 2)
assert.Len(t, pkg.OutRefs, 4)
testutils.AssertPresenceOfRef(t, lib, "Typ", pkg, "Typ", goref.Instantiation, true)
testutils.AssertPresenceOfRef(t, lib, "Fun", pkg, "Fun", goref.Call, true)

basepred := testutils.EqualRefPred(&goref.Ref{
FromPackage: pkg,
FromIdent: ".",
ToPackage: lib,
ToIdent: "lib",
RefType: goref.Import,
})
{
pospred := testutils.ToPositionFilenamePred("lib.go")
r := testutils.FindRefP(&lib.InRefs, func(r *goref.Ref) bool {
return basepred(r) && pospred(r)
})
assert.NotNil(t, r)
}
{
pospred := testutils.ToPositionFilenamePred("lib2.go")
r := testutils.FindRefP(&lib.InRefs, func(r *goref.Ref) bool {
return basepred(r) && pospred(r)
})
assert.NotNil(t, r)
}
}
10 changes: 0 additions & 10 deletions main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,6 @@ func main() {
log.Printf("%d packages in the graph\n", len(m.Packages))
log.Printf("%d files in the graph\n", len(m.Files))

log.Printf("Packages that depend on `fmt`:\n")
for d, _ := range m.Packages["fmt"].Dependents {
log.Printf(" - %s\n", d)
}

log.Printf("Packages that `goref` depends on:\n")
for d, _ := range m.Packages["github.com/korfuri/goref"].Dependencies {
log.Printf(" - %s\n", d)
}

log.Printf("Package `goref` has these files:\n")
for d, _ := range m.Packages["github.com/korfuri/goref"].Files {
log.Printf(" - %s\n", d)
Expand Down
25 changes: 7 additions & 18 deletions package.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,6 @@ type Package struct {
// Name of the package
Name string

// Dependencies is the map of load-paths imported within this
// package to the corresponding Package objects.
Dependencies map[string]*Package

// Dependents is the map of packages' load-paths that load
// this package through any load-path, mapped to their
// corresponding Package objects.
Dependents map[string]*Package

// Files is a map of paths to File objects that make up this package.
Files map[string]*File

Expand Down Expand Up @@ -64,14 +55,12 @@ func (p *Package) String() string {
func newPackage(pi *loader.PackageInfo, fset *token.FileSet) *Package {
return &Package{
//PackageInfo: pi,
Name: pi.Pkg.Name(),
Dependencies: make(map[string]*Package),
Dependents: make(map[string]*Package),
Files: make(map[string]*File),
OutRefs: make([]*Ref, 0),
InRefs: make([]*Ref, 0),
Interfaces: make([]*types.Named, 0),
Impls: make([]*types.Named, 0),
Fset: fset,
Name: pi.Pkg.Name(),
Files: make(map[string]*File),
OutRefs: make([]*Ref, 0),
InRefs: make([]*Ref, 0),
Interfaces: make([]*types.Named, 0),
Impls: make([]*types.Named, 0),
Fset: fset,
}
}
31 changes: 29 additions & 2 deletions packagegraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,35 @@ func (pg *PackageGraph) loadPackage(prog *loader.Program, loadpath string, pi *l
importedPkg := pg.loadPackage(prog, ipath, i)

// Set up the edges on the package dependency graph
importedPkg.Dependents[loadpath] = pkg
pkg.Dependencies[ipath] = importedPkg
var importAs string
// If the import is unqualified
if imported.Name == nil {
importAs = i.Pkg.Name()
} else {
importAs = imported.Name.String()
}

// Create a Ref to each file in the imported
// package. This is useful in two ways:
// reverse lookups can happen from any file in
// a package, and users are free to decide
// what file(s) they want to look up after
// finding `Import` OutRefs in a package.
for _, f := range i.Files {
if f.Name != nil {
r := &Ref{
RefType: Import,
FromPosition: NewPosition(prog.Fset, imported.Pos(), imported.End()),
ToPosition: NewPosition(prog.Fset, f.Name.Pos(), f.Name.End()),
FromIdent: importAs,
ToIdent: i.Pkg.Name(),
FromPackage: pkg,
ToPackage: importedPkg,
}
pkg.OutRefs = append(pkg.OutRefs, r)
importedPkg.InRefs = append(importedPkg.InRefs, r)
}
}
}
}

Expand Down
5 changes: 4 additions & 1 deletion ref.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@ type Ref struct {
}

func (r *Ref) String() string {
return fmt.Sprintf("%s of to:`%s.%s` at %s by from:`%s.%s` at %s", r.RefType, r.ToPackage, r.ToIdent, r.ToPosition, r.FromPackage, r.FromIdent, r.FromPosition)
return fmt.Sprintf("%s of to:`%s.%s` at %s by from:`%s.%s` at %s",
r.RefType,
r.ToPackage, r.ToIdent, r.ToPosition,
r.FromPackage, r.FromIdent, r.FromPosition)
}
7 changes: 7 additions & 0 deletions reftype.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ const (
// Extension of an interface by another interface.
Extension

// Import is the import of a package by another. `fromIdent`
// may differ from the name of the target package in the case
// of named imports. For dot-imports, `fromIdent` is ".".
Import

// Reference is the default, used when we can't determine the
// type of reference.
Reference
Expand All @@ -40,6 +45,8 @@ func (rt RefType) String() string {
return "Implementation"
case Extension:
return "Extension"
case Import:
return "Import"
case Reference:
return "Reference"
}
Expand Down
16 changes: 6 additions & 10 deletions simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/korfuri/goref"
"github.com/korfuri/goref/testutils"
"github.com/stretchr/testify/assert"
)

Expand All @@ -19,20 +20,15 @@ func TestSimplePackage(t *testing.T) {
assert.Contains(t, pg.Packages, "fmt")
pkg := pg.Packages[pkgpath]
assert.Empty(t, pkg.InRefs)
assert.Len(t, pkg.OutRefs, 1)
r := pkg.OutRefs[0]

testutils.AssertPresenceOfRef(t, pg.Packages["fmt"], "fmt", pkg, "fmt", goref.Import, true)

r := testutils.GetRef(t, pg.Packages["fmt"], "Println", pkg, "Println", goref.Call)
assert.NotNil(t, r)
p := r.FromPosition
assert.Equal(t, 6, p.PosL)
assert.Equal(t, 6, p.PosC)
assert.Equal(t, 6, p.EndL)
assert.Equal(t, 13, p.EndC)
assert.Contains(t, p.File, filepath)

assert.True(t, goref.Call == r.RefType)
assert.Equal(t, r.FromPosition.PosL, 6)
assert.Equal(t, r.FromPosition.PosC, 6)
assert.Contains(t, r.FromPosition.File, filepath)
assert.Equal(t, "Println", r.ToIdent)
assert.Equal(t, pg.Packages["fmt"], r.ToPackage)
assert.Equal(t, pkg, r.FromPackage)
}
2 changes: 0 additions & 2 deletions testprograms/dotimports/lib/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,3 @@ package lib

func Fun() {
}

type Typ struct{}
3 changes: 3 additions & 0 deletions testprograms/dotimports/lib/lib2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package lib

type Typ struct{}
38 changes: 36 additions & 2 deletions testutils/testutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package testutils

import (
"fmt"
"strings"
"testing"

"github.com/korfuri/goref"
Expand All @@ -11,12 +12,17 @@ import (
// Asserts that a collection of Ref contains at least one element that
// satisfies a given predicate.
func ContainsRefP(slc *[]*goref.Ref, pred func(*goref.Ref) bool) bool {
return FindRefP(slc, pred) != nil
}

// FindRefP returns the first Ref in a colletion that satisfies the given predicate
func FindRefP(slc *[]*goref.Ref, pred func(*goref.Ref) bool) *goref.Ref {
for _, r := range *slc {
if pred(r) {
return true
return r
}
}
return false
return nil
}

// stringifyRefslice returns a string representation of a []*Ref.
Expand All @@ -40,6 +46,16 @@ func EqualRefPred(compRef *goref.Ref) func(*goref.Ref) bool {
}
}

// ToPositionFilenamePred returns a predicated that accepts a *Ref as
// argument and returns whether the Ref's ToPosition's Filename ends
// with `suffix`.
func ToPositionFilenamePred(suffix string) func(*goref.Ref) bool {
return func(r *goref.Ref) bool {
f := r.ToPosition.File
return strings.HasSuffix(f, suffix)
}
}

// Asserts that there exists a ref (should=true) or doesn't exist any
// ref (should=false) of type RefType from package fromPkg, from
// identifier fromId, to package toPkg, to identifier toId. This
Expand All @@ -62,3 +78,21 @@ func AssertPresenceOfRef(t *testing.T, toPkg *goref.Package, toId string, fromPk
assert.False(t, ContainsRefP(&fromPkg.OutRefs, pred), "implPkg(%s).OutRefs contains a Ref matching the forbidden Ref: [%s].\nOutRefs was: \n%s", fromPkg, refref.String(), stringifyRefslice(fromPkg.OutRefs))
}
}

// GetRef finds a ref in the InRefs of the provided package that
// matches the provided parameters. It fails the test if no such ref
// can be found or if the ref doesn't exist in the corresponding
// OutRefs.
func GetRef(t *testing.T, toPkg *goref.Package, toId string, fromPkg *goref.Package, fromId string, reftype goref.RefType) *goref.Ref {
pred := EqualRefPred(&goref.Ref{
FromPackage: fromPkg,
FromIdent: fromId,
ToPackage: toPkg,
ToIdent: toId,
RefType: reftype,
})
r := FindRefP(&toPkg.InRefs, pred)
assert.NotNil(t, r)
assert.Contains(t, fromPkg.OutRefs, r)
return r
}

0 comments on commit 5ffe092

Please sign in to comment.