<commit_before>/*
  Go Language Raspberry Pi Interface
  (c) Copyright David Thorpe 2016-2017
  All Rights Reserved

  Documentation http://djthorpe.github.io/gopi/
  For Licensing and Usage information, please see LICENSE.md
*/

package rpc

import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"reflect"
	"time"

	// Frameworks
	gopi "github.com/djthorpe/gopi"
	grpc "google.golang.org/grpc"
	credentials "google.golang.org/grpc/credentials"
	reflection_pb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
)

// ClientConn is the RPC client connection
type ClientConn struct {
	Addr       string
	SSL        bool
	SkipVerify bool
	Timeout    time.Duration // Connection timeout
}

type clientconn struct {
	log        gopi.Logger
	addr       string
	ssl        bool
	skipverify bool
	timeout    time.Duration
	conn       *grpc.ClientConn
}

////////////////////////////////////////////////////////////////////////////////
// CLIENT OPEN AND CLOSE

// Open a client
func (config ClientConn) Open(log gopi.Logger) (gopi.Driver, error) {
	log.Debug("<grpc.ClientConn>Open(addr=%v,ssl=%v,skipverify=%v,timeout=%v)", config.Addr, config.SSL, config.SkipVerify, config.Timeout)

	// Create a client object
	this := new(clientconn)
	this.addr = config.Addr
	this.ssl = config.SSL
	this.skipverify = config.SkipVerify
	this.timeout = config.Timeout
	this.log = log
	this.conn = nil

	// success
	return this, nil
}

// Close client
func (this *clientconn) Close() error {
	this.log.Debug("<grpc.ClientConn>Close{ addr=%v }", this.addr)
	return this.Disconnect()
}

////////////////////////////////////////////////////////////////////////////////
// RPCClientConn interface implementation

func (this *clientconn) Connect() ([]string, error) {
	this.log.Debug2("<grpc.ClientConn>Connect{ addr=%v }", this.addr)
	if this.conn != nil {
		return nil, errors.New("Cannot call Connect() when connection already made")
	}
	opts := make([]grpc.DialOption, 0, 1)

	// SSL options
	if this.ssl {
		opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: this.skipverify})))
	} else {
		opts = append(opts, grpc.WithInsecure())
	}

	// Connection timeout options
	if this.timeout > 0 {
		opts = append(opts, grpc.WithTimeout(this.timeout))
	}

	// Dial connection
	if conn, err := grpc.Dial(this.addr, opts...); err != nil {
		return nil, err
	} else {
		this.conn = conn
	}

	// Get services
	reflection := this.newServerReflectionClient()
	if reflection == nil {
		this.log.Warn("grpc.ClientConn: Unable to create reflection client")
		return nil, nil
	}
	defer reflection.CloseSend()

	if services, err := this.listServices(reflection); err != nil {
		this.log.Warn("grpc.ClientConn: %v", err)
		return nil, nil
	} else {
		return services, nil
	}
}

func (this *clientconn) Disconnect() error {
	this.log.Debug2("<grpc.ClientConn>Disconnect{ addr=%v }", this.addr)

	if this.conn != nil {
		err := this.conn.Close()
		this.conn = nil
		return err
	}
	return nil
}

func (this *clientconn) NewService(constructor reflect.Value) (interface{}, error) {
	this.log.Debug2("<grpc.ClientConn>NewService{ func=%v }", constructor)

	if constructor.Kind() != reflect.Func {
		return nil, gopi.ErrBadParameter
	}

	if service := constructor.Call([]reflect.Value{reflect.ValueOf(this.conn)}); len(service) != 1 {
		return nil, gopi.ErrBadParameter
	} else {
		return service[0].Interface(), nil
	}
}

////////////////////////////////////////////////////////////////////////////////
// STRINGIFY

func (this *clientconn) String() string {
	if this.conn != nil {
		return fmt.Sprintf("<grpc.ClientConn>{ connected=true addr=%v ssl=%v skipverify=%v }", this.addr, this.ssl, this.skipverify)
	} else {
		return fmt.Sprintf("<grpc.ClientConn>{ connected=false addr=%v ssl=%v skipverify=%v }", this.addr, this.ssl, this.skipverify)
	}
}

////////////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS

func (this *clientconn) newServerReflectionClient() reflection_pb.ServerReflection_ServerReflectionInfoClient {
	if this.conn == nil {
		return nil
	}
	ctx := context.Background()
	if this.timeout > 0 {
		ctx, _ = context.WithTimeout(ctx, this.timeout)
	}
	if client, err := reflection_pb.NewServerReflectionClient(this.conn).ServerReflectionInfo(ctx); err != nil {
		this.log.Error("Error: %v", err)
		return nil
	} else {
		return client
	}
}

func (this *clientconn) listServices(c reflection_pb.ServerReflection_ServerReflectionInfoClient) ([]string, error) {
	if err := c.Send(&reflection_pb.ServerReflectionRequest{
		MessageRequest: &reflection_pb.ServerReflectionRequest_ListServices{},
	}); err != nil {
		return nil, err
	}

	if resp, err := c.Recv(); err != nil {
		return nil, err
	} else if modules := resp.GetListServicesResponse(); modules == nil {
		return nil, fmt.Errorf("GetListServicesResponse() error")
	} else {
		module_services := modules.GetService()
		module_names := make([]string, len(module_services))
		for i, service := range module_services {
			module_names[i] = service.Name
		}
		return module_names, nil
	}
}
<commit_msg>Updated the reflection client<commit_after>/*
  Go Language Raspberry Pi Interface
  (c) Copyright David Thorpe 2016-2017
  All Rights Reserved

  Documentation http://djthorpe.github.io/gopi/
  For Licensing and Usage information, please see LICENSE.md
*/

package rpc

import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"reflect"
	"time"

	// Frameworks
	gopi "github.com/djthorpe/gopi"
	grpc "google.golang.org/grpc"
	credentials "google.golang.org/grpc/credentials"
	reflection_pb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
)

// ClientConn is the RPC client connection
type ClientConn struct {
	Addr       string
	SSL        bool
	SkipVerify bool
	Timeout    time.Duration // Connection timeout
}

type clientconn struct {
	log        gopi.Logger
	addr       string
	ssl        bool
	skipverify bool
	timeout    time.Duration
	conn       *grpc.ClientConn
}

////////////////////////////////////////////////////////////////////////////////
// CLIENT OPEN AND CLOSE

// Open a client
func (config ClientConn) Open(log gopi.Logger) (gopi.Driver, error) {
	log.Debug("<grpc.ClientConn>Open(addr=%v,ssl=%v,skipverify=%v,timeout=%v)", config.Addr, config.SSL, config.SkipVerify, config.Timeout)

	// Create a client object
	this := new(clientconn)
	this.addr = config.Addr
	this.ssl = config.SSL
	this.skipverify = config.SkipVerify
	this.timeout = config.Timeout
	this.log = log
	this.conn = nil

	// success
	return this, nil
}

// Close client
func (this *clientconn) Close() error {
	this.log.Debug("<grpc.ClientConn>Close{ addr=%v }", this.addr)
	return this.Disconnect()
}

////////////////////////////////////////////////////////////////////////////////
// RPCClientConn interface implementation

func (this *clientconn) Connect() ([]string, error) {
	this.log.Debug2("<grpc.ClientConn>Connect{ addr=%v }", this.addr)
	if this.conn != nil {
		return nil, errors.New("Cannot call Connect() when connection already made")
	}
	opts := make([]grpc.DialOption, 0, 1)

	// SSL options
	if this.ssl {
		opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: this.skipverify})))
	} else {
		opts = append(opts, grpc.WithInsecure())
	}

	// Connection timeout options
	if this.timeout > 0 {
		opts = append(opts, grpc.WithTimeout(this.timeout))
	}

	// Dial connection
	if conn, err := grpc.Dial(this.addr, opts...); err != nil {
		return nil, err
	} else {
		this.conn = conn
	}

	// Get services
	if reflection, err := this.newServerReflectionClient(); err != nil {
		return nil, err
	} else {
		defer reflection.CloseSend()
		if services, err := this.listServices(reflection); err != nil {
			this.log.Warn("grpc.ClientConn: %v", err)
			return nil, nil
		} else {
			return services, nil
		}
	}
}

func (this *clientconn) Disconnect() error {
	this.log.Debug2("<grpc.ClientConn>Disconnect{ addr=%v }", this.addr)

	if this.conn != nil {
		err := this.conn.Close()
		this.conn = nil
		return err
	}
	return nil
}

func (this *clientconn) NewService(constructor reflect.Value) (interface{}, error) {
	this.log.Debug2("<grpc.ClientConn>NewService{ func=%v }", constructor)

	if constructor.Kind() != reflect.Func {
		return nil, gopi.ErrBadParameter
	}

	if service := constructor.Call([]reflect.Value{reflect.ValueOf(this.conn)}); len(service) != 1 {
		return nil, gopi.ErrBadParameter
	} else {
		return service[0].Interface(), nil
	}
}

////////////////////////////////////////////////////////////////////////////////
// STRINGIFY

func (this *clientconn) String() string {
	if this.conn != nil {
		return fmt.Sprintf("<grpc.ClientConn>{ connected=true addr=%v ssl=%v skipverify=%v }", this.addr, this.ssl, this.skipverify)
	} else {
		return fmt.Sprintf("<grpc.ClientConn>{ connected=false addr=%v ssl=%v skipverify=%v }", this.addr, this.ssl, this.skipverify)
	}
}

////////////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS

func (this *clientconn) newServerReflectionClient() (reflection_pb.ServerReflection_ServerReflectionInfoClient, error) {
	if this.conn == nil {
		return nil, gopi.ErrOutOfOrder
	}
	ctx := context.Background()
	if this.timeout > 0 {
		ctx, _ = context.WithTimeout(ctx, this.timeout)
	}
	if client, err := reflection_pb.NewServerReflectionClient(this.conn).ServerReflectionInfo(ctx); err != nil {
		return nil, err
	} else {
		return client, nil
	}
}

func (this *clientconn) listServices(c reflection_pb.ServerReflection_ServerReflectionInfoClient) ([]string, error) {
	if err := c.Send(&reflection_pb.ServerReflectionRequest{
		MessageRequest: &reflection_pb.ServerReflectionRequest_ListServices{},
	}); err != nil {
		return nil, err
	}

	if resp, err := c.Recv(); err != nil {
		return nil, err
	} else if modules := resp.GetListServicesResponse(); modules == nil {
		return nil, fmt.Errorf("GetListServicesResponse() error")
	} else {
		module_services := modules.GetService()
		module_names := make([]string, len(module_services))
		for i, service := range module_services {
			module_names[i] = service.Name
		}
		return module_names, nil
	}
}
<|endoftext|>


<commit_before>// 1 march 2014
package ui

import (
	"fmt"
	"unsafe"
)

// #cgo LDFLAGS: -lobjc -framework Foundation -framework AppKit
// #include "objc_darwin.h"
import "C"

type sysData struct {
	cSysData

	id	C.id
}

type classData struct {
	make		func(parentWindow C.id) C.id
	show		func(what C.id)
	hide			func(what C.id)
	settextsel		C.SEL
	textsel		C.SEL
}

var (
	_NSWindow = objc_getClass("NSWindow")

	_initWithContentRect = sel_getUid("initWithContentRect:styleMask:backing:defer:")
	_makeKeyAndOrderFront = sel_getUid("makeKeyAndOrderFront:")
	_orderOut = sel_getUid("orderOut:")
	_setHidden = sel_getUid("setHidden:")
	_setTitle = sel_getUid("setTitle:")
	_setStringValue = sel_getUid("setStringValue:")
	_setFrame = sel_getUid("setFrame:")
	_state = sel_getUid("state")
	_title = sel_getUid("title")
	_stringValue = sel_getUid("stringValue")
	// TODO others
	_frame = sel_getUid("frame")
	_setFrameDisplay = sel_getUid("setFrame:display:")
)

func controlShow(what C.id) {
	C.objc_msgSend_bool(what, _setHidden, C.BOOL(C.NO))
}

func controlHide(what C.id) {
	C.objc_msgSend_bool(what, _setHidden, C.BOOL(C.YES))
}

var classTypes = [nctypes]*classData{
	c_window:		&classData{
		make:		func(parentWindow C.id) C.id {
			const (
				NSBorderlessWindowMask = 0
				NSTitledWindowMask = 1 << 0
				NSClosableWindowMask = 1 << 1
				NSMiniaturizableWindowMask = 1 << 2
				NSResizableWindowMask = 1 << 3
				NSTexturedBackgroundWindowMask = 1 << 8
			)

			// we have to specify a content rect to start; it will be overridden soon though
			win := objc_alloc(_NSWindow)
			return objc_msgSend_rect_uint_uint_bool(win,
				_initWithContentRect,
				0, 0, 100, 100,
				NSTitledWindowMask | NSClosableWindowMask | NSClosableWindowMask | NSResizableWindowMask,
				2,					// NSBackingStoreBuffered - the only backing store method that Apple says we should use (the others are legacy)
				C.BOOL(C.YES))			// defer creation of device until we show the window
		},
		show:		func(what C.id) {
			C.objc_msgSend_id(what, _makeKeyAndOrderFront, what)
		},
		hide:			func(what C.id) {
			C.objc_msgSend_id(what, _orderOut, what)
		},
		settextsel:		_setTitle,
		textsel:		_title,
	},
	c_button:			&classData{
	},
	c_checkbox:		&classData{
	},
	c_combobox:		&classData{
	},
	c_lineedit:		&classData{
	},
	c_label:			&classData{
	},
	c_listbox:			&classData{
	},
	c_progressbar:		&classData{
	},
}

func (s *sysData) make(initText string, window *sysData) error {
	var parentWindow C.id

	ct := classTypes[s.ctype]
	if ct.make == nil {
		println(s.ctype, "not implemented")
		return nil
	}
	if window != nil {
		parentWindow = window.id
	}
	ret := make(chan C.id)
	defer close(ret)
	uitask <- func() {
		ret <- ct.make(parentWindow)
	}
	s.id = <-ret
	err := s.setText(initText)
	if err != nil {
		return fmt.Errorf("error setting initial text of new window/control: %v", err)
	}
	return nil
}

func (s *sysData) show() error {
if classTypes[s.ctype].show == nil { return nil }
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		classTypes[s.ctype].show(s.id)
		ret <- struct{}{}
	}
	<-ret
	return nil
}

func (s *sysData) hide() error {
if classTypes[s.ctype].hide == nil { return nil }
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		classTypes[s.ctype].hide(s.id)
		ret <- struct{}{}
	}
	<-ret
	return nil
}

func (s *sysData) setText(text string) error {
var zero C.SEL
if classTypes[s.ctype].settextsel == zero { return nil }
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		C.objc_msgSend_id(s.id, classTypes[s.ctype].settextsel, toNSString(text))
		ret <- struct{}{}
	}
	<-ret
	return nil
}

func (s *sysData) setRect(x int, y int, width int, height int) error {
if classTypes[s.ctype].make == nil { return nil }
	objc_msgSend_rect(s.id, _setFrame, x, y, width, height)
	return nil
}

func (s *sysData) isChecked() bool {
if classTypes[s.ctype].make == nil { return false }
	const (
		NSOnState = 1
	)

	ret := make(chan bool)
	defer close(ret)
	uitask <- func() {
		k := C.objc_msgSend_noargs(s.id, _state)
		ret <- uintptr(unsafe.Pointer(k)) == NSOnState
	}
	return <-ret
}

func (s *sysData) text() string {
var zero C.SEL
if classTypes[s.ctype].textsel == zero { return "" }
	ret := make(chan string)
	defer close(ret)
	uitask <- func() {
		str := C.objc_msgSend_noargs(s.id, classTypes[s.ctype].textsel)
		ret <- fromNSString(str)
	}
	return <-ret
}

func (s *sysData) append(what string) error {
	// TODO
	return nil
}

func (s *sysData) insertBefore(what string, before int) error {
	// TODO
	return nil
}

func (s *sysData) selectedIndex() int {
	// TODO
	return -1
}

func (s *sysData) selectedIndices() []int {
	// TODO
	return nil
}

func (s *sysData) selectedTexts() []string {
	// TODO
	return nil
}

func (s *sysData) setWindowSize(width int, height int) error {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		// we need to get the top left point
		r := C.objc_msgSend_stret_rect_noargs(s.id, _frame)
		objc_msgSend_rect_bool(s.id, _setFrameDisplay,
			int(r.x), int(r.y), width, height,
			C.BOOL(C.YES))		// TODO set to NO to prevent subviews from being redrawn before they are resized?
		ret <- struct{}{}
	}
	<-ret
	return nil
}

func (s *sysData) delete(index int) error {
	// TODO
	return nil
}

func (s *sysData) setProgress(percent int) {
	// TODO
}
<commit_msg>Added the getSysData() hook. Now to clean up loose ends and apply the delegate to the window.<commit_after>// 1 march 2014
package ui

import (
	"fmt"
	"unsafe"
	"sync"
)

// #cgo LDFLAGS: -lobjc -framework Foundation -framework AppKit
// #include "objc_darwin.h"
import "C"

type sysData struct {
	cSysData

	id	C.id
}

type classData struct {
	make		func(parentWindow C.id) C.id
	show		func(what C.id)
	hide			func(what C.id)
	settextsel		C.SEL
	textsel		C.SEL
}

var (
	_NSWindow = objc_getClass("NSWindow")

	_initWithContentRect = sel_getUid("initWithContentRect:styleMask:backing:defer:")
	_makeKeyAndOrderFront = sel_getUid("makeKeyAndOrderFront:")
	_orderOut = sel_getUid("orderOut:")
	_setHidden = sel_getUid("setHidden:")
	_setTitle = sel_getUid("setTitle:")
	_setStringValue = sel_getUid("setStringValue:")
	_setFrame = sel_getUid("setFrame:")
	_state = sel_getUid("state")
	_title = sel_getUid("title")
	_stringValue = sel_getUid("stringValue")
	// TODO others
	_frame = sel_getUid("frame")
	_setFrameDisplay = sel_getUid("setFrame:display:")
)

func controlShow(what C.id) {
	C.objc_msgSend_bool(what, _setHidden, C.BOOL(C.NO))
}

func controlHide(what C.id) {
	C.objc_msgSend_bool(what, _setHidden, C.BOOL(C.YES))
}

var classTypes = [nctypes]*classData{
	c_window:		&classData{
		make:		func(parentWindow C.id) C.id {
			const (
				NSBorderlessWindowMask = 0
				NSTitledWindowMask = 1 << 0
				NSClosableWindowMask = 1 << 1
				NSMiniaturizableWindowMask = 1 << 2
				NSResizableWindowMask = 1 << 3
				NSTexturedBackgroundWindowMask = 1 << 8
			)

			// we have to specify a content rect to start; it will be overridden soon though
			win := objc_alloc(_NSWindow)
			return objc_msgSend_rect_uint_uint_bool(win,
				_initWithContentRect,
				0, 0, 100, 100,
				NSTitledWindowMask | NSClosableWindowMask | NSClosableWindowMask | NSResizableWindowMask,
				2,					// NSBackingStoreBuffered - the only backing store method that Apple says we should use (the others are legacy)
				C.BOOL(C.YES))			// defer creation of device until we show the window
		},
		show:		func(what C.id) {
			C.objc_msgSend_id(what, _makeKeyAndOrderFront, what)
		},
		hide:			func(what C.id) {
			C.objc_msgSend_id(what, _orderOut, what)
		},
		settextsel:		_setTitle,
		textsel:		_title,
	},
	c_button:			&classData{
	},
	c_checkbox:		&classData{
	},
	c_combobox:		&classData{
	},
	c_lineedit:		&classData{
	},
	c_label:			&classData{
	},
	c_listbox:			&classData{
	},
	c_progressbar:		&classData{
	},
}

// I need to access sysData from appDelegate, but appDelegate doesn't store any data. So, this.
var (
	sysdatas = make(map[C.id]*sysData)
	sysdatalock sync.Mutex
)

func addSysData(key C.id, value *sysData) {
	sysdatalock.Lock()
	sysdatas[key] = value
	sysdatalock.Unlock()
}

func getSysData(key C.id) *sysData {
	sysdatalock.Lock()
	defer sysdatalock.Unlock()

	v, ok := sysdatas[key]
	if !ok {
		panic(fmt.Errorf("internal error: getSysData(%v) unknown", key))
	}
	return v
}

func (s *sysData) make(initText string, window *sysData) error {
	var parentWindow C.id

	ct := classTypes[s.ctype]
	if ct.make == nil {
		println(s.ctype, "not implemented")
		return nil
	}
	if window != nil {
		parentWindow = window.id
	}
	ret := make(chan C.id)
	defer close(ret)
	uitask <- func() {
		ret <- ct.make(parentWindow)
	}
	s.id = <-ret
	err := s.setText(initText)
	if err != nil {
		return fmt.Errorf("error setting initial text of new window/control: %v", err)
	}
	addSysData(s.id, s)
	return nil
}

func (s *sysData) show() error {
if classTypes[s.ctype].show == nil { return nil }
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		classTypes[s.ctype].show(s.id)
		ret <- struct{}{}
	}
	<-ret
	return nil
}

func (s *sysData) hide() error {
if classTypes[s.ctype].hide == nil { return nil }
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		classTypes[s.ctype].hide(s.id)
		ret <- struct{}{}
	}
	<-ret
	return nil
}

func (s *sysData) setText(text string) error {
var zero C.SEL
if classTypes[s.ctype].settextsel == zero { return nil }
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		C.objc_msgSend_id(s.id, classTypes[s.ctype].settextsel, toNSString(text))
		ret <- struct{}{}
	}
	<-ret
	return nil
}

func (s *sysData) setRect(x int, y int, width int, height int) error {
if classTypes[s.ctype].make == nil { return nil }
	objc_msgSend_rect(s.id, _setFrame, x, y, width, height)
	return nil
}

func (s *sysData) isChecked() bool {
if classTypes[s.ctype].make == nil { return false }
	const (
		NSOnState = 1
	)

	ret := make(chan bool)
	defer close(ret)
	uitask <- func() {
		k := C.objc_msgSend_noargs(s.id, _state)
		ret <- uintptr(unsafe.Pointer(k)) == NSOnState
	}
	return <-ret
}

func (s *sysData) text() string {
var zero C.SEL
if classTypes[s.ctype].textsel == zero { return "" }
	ret := make(chan string)
	defer close(ret)
	uitask <- func() {
		str := C.objc_msgSend_noargs(s.id, classTypes[s.ctype].textsel)
		ret <- fromNSString(str)
	}
	return <-ret
}

func (s *sysData) append(what string) error {
	// TODO
	return nil
}

func (s *sysData) insertBefore(what string, before int) error {
	// TODO
	return nil
}

func (s *sysData) selectedIndex() int {
	// TODO
	return -1
}

func (s *sysData) selectedIndices() []int {
	// TODO
	return nil
}

func (s *sysData) selectedTexts() []string {
	// TODO
	return nil
}

func (s *sysData) setWindowSize(width int, height int) error {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		// we need to get the top left point
		r := C.objc_msgSend_stret_rect_noargs(s.id, _frame)
		objc_msgSend_rect_bool(s.id, _setFrameDisplay,
			int(r.x), int(r.y), width, height,
			C.BOOL(C.YES))		// TODO set to NO to prevent subviews from being redrawn before they are resized?
		ret <- struct{}{}
	}
	<-ret
	return nil
}

func (s *sysData) delete(index int) error {
	// TODO
	return nil
}

func (s *sysData) setProgress(percent int) {
	// TODO
}
<|endoftext|>


<commit_before>package postgis

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"

	"database/sql"
	gostErrors "github.com/geodan/gost/src/errors"
	"github.com/geodan/gost/src/sensorthings/entities"
)

// GetDatastream todo
func (gdb *GostDatabase) GetDatastream(id string) (*entities.Datastream, error) {
	intID, err := strconv.Atoi(id)
	if err != nil {
		return nil, err
	}

	sql := fmt.Sprintf("select id, description, unitofmeasurement, public.ST_AsGeoJSON(observedarea) FROM %s.datastream where id = $1", gdb.Schema)
	datastream, err := processDatastream(gdb.Db, sql, intID)
	if err != nil {
		return nil, err
	}

	return datastream, nil
}

// GetDatastreams retrieves all datastreams
func (gdb *GostDatabase) GetDatastreams() ([]*entities.Datastream, error) {
	sql := fmt.Sprintf("select id, description, unitofmeasurement, public.ST_AsGeoJSON(observedarea) FROM datastream", gdb.Schema)
	return processDatastreams(gdb.Db, sql)
}

// GetDatastreamsByThing retrieves all datastreams linked to the given thing
func (gdb *GostDatabase) GetDatastreamsByThing(thingID string) ([]*entities.Datastream, error) {
	tID, err := strconv.Atoi(thingID)
	if err != nil {
		return nil, err
	}

	sql := fmt.Sprintf("select datastream.id, datastream.description, datastream.unitofmeasurement, public.ST_AsGeoJSON(datastream.observedarea) FROM %s.datastream inner join %s.thing on thing.id = datastream.thing_id where thing.id = $1", gdb.Schema, gdb.Schema)
	return processDatastreams(gdb.Db, sql, tID)
}

func processDatastream(db *sql.DB, sql string, args ...interface{}) (*entities.Datastream, error) {
	datastreams, err := processDatastreams(db, sql, args...)
	if err != nil {
		return nil, err
	}

	if len(datastreams) == 0 {
		return nil, gostErrors.NewRequestNotFound(errors.New("Datastream not found"))
	}

	return datastreams[0], nil
}

func processDatastreams(db *sql.DB, sql string, args ...interface{}) ([]*entities.Datastream, error) {
	rows, err := db.Query(sql, args...)
	defer rows.Close()

	if err != nil {
		return nil, err
	}

	var datastreams = []*entities.Datastream{}
	for rows.Next() {
		var id int
		var description, unitofmeasurement string
		var observedarea *string

		err := rows.Scan(&id, &description, &unitofmeasurement, &observedarea)
		if err != nil {
			return nil, err
		}

		unitOfMeasurementMap, err := JSONToMap(&unitofmeasurement)
		if err != nil {
			return nil, err
		}

		observedAreaMap, err := JSONToMap(observedarea)
		if err != nil {
			return nil, err
		}

		datastream := entities.Datastream{
			ID:                strconv.Itoa(id),
			Description:       description,
			UnitOfMeasurement: unitOfMeasurementMap,
			ObservedArea:      observedAreaMap,
		}
		datastreams = append(datastreams, &datastream)
	}

	return datastreams, nil
}

// PostDatastream todo
// TODO: !!!!ADD phenomenonTime SUPPORT!!!!
// TODO: !!!!ADD resulttime SUPPORT!!!!
// TODO: !!!!ADD observationtype SUPPORT!!!!
func (gdb *GostDatabase) PostDatastream(d entities.Datastream) (*entities.Datastream, error) {
	var dsID int
	tID, err := strconv.Atoi(d.Thing.ID)
	if err != nil || !gdb.ThingExists(tID) {
		return nil, gostErrors.NewBadRequestError(errors.New("Thing does not exist"))
	}

	sID, err := strconv.Atoi(d.Sensor.ID)
	if err != nil || !gdb.SensorExists(sID) {
		return nil, gostErrors.NewBadRequestError(errors.New("Sensor does not exist"))
	}

	oID, err := strconv.Atoi(d.ObservedProperty.ID)
	if err != nil || !gdb.ObservedPropertyExists(oID) {
		return nil, gostErrors.NewBadRequestError(errors.New("ObservedProperty does not exist"))
	}

	unitOfMeasurement, _ := json.Marshal(d.UnitOfMeasurement)
	geom := "NULL"
	if len(d.ObservedArea) != 0 {
		observedAreaBytes, _ := json.Marshal(d.ObservedArea)
		geom = fmt.Sprintf("public.ST_GeomFromGeoJSON('%s')", string(observedAreaBytes[:]))
	}

	sql := fmt.Sprintf("INSERT INTO %s.datastream (description, unitofmeasurement, observedarea, thing_id, sensor_id, observerproperty_id) VALUES ($1, $2, %s, $3, $4, $5) RETURNING id", gdb.Schema, geom)
	err = gdb.Db.QueryRow(sql, d.Description, unitOfMeasurement, tID, sID, oID).Scan(&dsID)
	if err != nil {
		return nil, err
	}

	d.ID = strconv.Itoa(dsID)

	// clear inner entities to serves links upon response
	d.Thing = nil
	d.Sensor = nil
	d.ObservedProperty = nil

	return &d, nil
}

// DatastreamExists checks if a Datastream is present in the database based on a given id
func (gdb *GostDatabase) DatastreamExists(databaseID int) bool {
	var result bool
	sql := fmt.Sprintf("SELECT exists (SELECT 1 FROM %s.datastream WHERE id = $1 LIMIT 1)", gdb.Schema)
	err := gdb.Db.QueryRow(sql, databaseID).Scan(&result)
	if err != nil {
		return false
	}

	return result
}
<commit_msg>fix GET Datastreams<commit_after>package postgis

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"

	"database/sql"
	gostErrors "github.com/geodan/gost/src/errors"
	"github.com/geodan/gost/src/sensorthings/entities"
)

// GetDatastream todo
func (gdb *GostDatabase) GetDatastream(id string) (*entities.Datastream, error) {
	intID, err := strconv.Atoi(id)
	if err != nil {
		return nil, err
	}

	sql := fmt.Sprintf("select id, description, unitofmeasurement, public.ST_AsGeoJSON(observedarea) AS observedarea FROM %s.datastream where id = $1", gdb.Schema)
	datastream, err := processDatastream(gdb.Db, sql, intID)
	if err != nil {
		return nil, err
	}

	return datastream, nil
}

// GetDatastreams retrieves all datastreams
func (gdb *GostDatabase) GetDatastreams() ([]*entities.Datastream, error) {
	sql := fmt.Sprintf("select id, description, unitofmeasurement, public.ST_AsGeoJSON(observedarea) AS observedarea FROM %s.datastream", gdb.Schema)
	return processDatastreams(gdb.Db, sql)
}

// GetDatastreamsByThing retrieves all datastreams linked to the given thing
func (gdb *GostDatabase) GetDatastreamsByThing(thingID string) ([]*entities.Datastream, error) {
	tID, err := strconv.Atoi(thingID)
	if err != nil {
		return nil, err
	}

	sql := fmt.Sprintf("select datastream.id, datastream.description, datastream.unitofmeasurement, public.ST_AsGeoJSON(datastream.observedarea) AS observedarea  FROM %s.datastream inner join %s.thing on thing.id = datastream.thing_id where thing.id = $1", gdb.Schema, gdb.Schema)
	return processDatastreams(gdb.Db, sql, tID)
}

func processDatastream(db *sql.DB, sql string, args ...interface{}) (*entities.Datastream, error) {
	datastreams, err := processDatastreams(db, sql, args...)
	if err != nil {
		return nil, err
	}

	if len(datastreams) == 0 {
		return nil, gostErrors.NewRequestNotFound(errors.New("Datastream not found"))
	}

	return datastreams[0], nil
}

func processDatastreams(db *sql.DB, sql string, args ...interface{}) ([]*entities.Datastream, error) {
	rows, err := db.Query(sql, args...)
	defer rows.Close()

	if err != nil {
		return nil, err
	}

	var datastreams = []*entities.Datastream{}
	for rows.Next() {
		var id int
		var description, unitofmeasurement string
		var observedarea *string

		err := rows.Scan(&id, &description, &unitofmeasurement, &observedarea)
		if err != nil {
			return nil, err
		}

		unitOfMeasurementMap, err := JSONToMap(&unitofmeasurement)
		if err != nil {
			return nil, err
		}

		observedAreaMap, err := JSONToMap(observedarea)
		if err != nil {
			return nil, err
		}

		datastream := entities.Datastream{
			ID:                strconv.Itoa(id),
			Description:       description,
			UnitOfMeasurement: unitOfMeasurementMap,
			ObservedArea:      observedAreaMap,
		}
		datastreams = append(datastreams, &datastream)
	}

	return datastreams, nil
}

// PostDatastream todo
// TODO: !!!!ADD phenomenonTime SUPPORT!!!!
// TODO: !!!!ADD resulttime SUPPORT!!!!
// TODO: !!!!ADD observationtype SUPPORT!!!!
func (gdb *GostDatabase) PostDatastream(d entities.Datastream) (*entities.Datastream, error) {
	var dsID int
	tID, err := strconv.Atoi(d.Thing.ID)
	if err != nil || !gdb.ThingExists(tID) {
		return nil, gostErrors.NewBadRequestError(errors.New("Thing does not exist"))
	}

	sID, err := strconv.Atoi(d.Sensor.ID)
	if err != nil || !gdb.SensorExists(sID) {
		return nil, gostErrors.NewBadRequestError(errors.New("Sensor does not exist"))
	}

	oID, err := strconv.Atoi(d.ObservedProperty.ID)
	if err != nil || !gdb.ObservedPropertyExists(oID) {
		return nil, gostErrors.NewBadRequestError(errors.New("ObservedProperty does not exist"))
	}

	unitOfMeasurement, _ := json.Marshal(d.UnitOfMeasurement)
	geom := "NULL"
	if len(d.ObservedArea) != 0 {
		observedAreaBytes, _ := json.Marshal(d.ObservedArea)
		geom = fmt.Sprintf("public.ST_GeomFromGeoJSON('%s')", string(observedAreaBytes[:]))
	}

	sql := fmt.Sprintf("INSERT INTO %s.datastream (description, unitofmeasurement, observedarea, thing_id, sensor_id, observerproperty_id) VALUES ($1, $2, %s, $3, $4, $5) RETURNING id", gdb.Schema, geom)
	err = gdb.Db.QueryRow(sql, d.Description, unitOfMeasurement, tID, sID, oID).Scan(&dsID)
	if err != nil {
		return nil, err
	}

	d.ID = strconv.Itoa(dsID)

	// clear inner entities to serves links upon response
	d.Thing = nil
	d.Sensor = nil
	d.ObservedProperty = nil

	return &d, nil
}

// DatastreamExists checks if a Datastream is present in the database based on a given id
func (gdb *GostDatabase) DatastreamExists(databaseID int) bool {
	var result bool
	sql := fmt.Sprintf("SELECT exists (SELECT 1 FROM %s.datastream WHERE id = $1 LIMIT 1)", gdb.Schema)
	err := gdb.Db.QueryRow(sql, databaseID).Scan(&result)
	if err != nil {
		return false
	}

	return result
}
<|endoftext|>


<commit_before>// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program.  If not, see <http://www.gnu.org/licenses/>.

package markdown

import "strings"

var referenceTerminatedBy []BlockRule

func ruleReference(s *StateBlock, startLine, _ int, silent bool) (_ bool) {
	pos := s.BMarks[startLine] + s.TShift[startLine]
	src := s.Src

	if src[pos] != '[' {
		return
	}

	pos++
	max := s.EMarks[startLine]

	for pos < max {
		if src[pos] == ']' && src[pos-1] != '\\' {
			if pos+1 == max {
				return
			}
			if src[pos+1] != ':' {
				return
			}
			break
		}
		pos++
	}

	nextLine := startLine + 1
	endLine := s.LineMax
outer:
	for ; nextLine < endLine && !s.IsLineEmpty(nextLine); nextLine++ {
		if s.TShift[nextLine]-s.BlkIndent > 3 {
			continue
		}

		for _, r := range referenceTerminatedBy {
			if r(s, nextLine, endLine, true) {
				break outer
			}
		}
	}

	str := strings.TrimSpace(s.Lines(startLine, nextLine, s.BlkIndent, false))
	max = len(str)
	lines := 0
	var labelEnd int
	for pos = 1; pos < max; pos++ {
		b := str[pos]
		if b == '[' {
			return
		} else if b == ']' {
			labelEnd = pos
			break
		} else if b == '\n' {
			lines++
		} else if b == '\\' {
			pos++
			if pos < max && str[pos] == '\n' {
				lines++
			}
		}
	}

	if labelEnd <= 0 || labelEnd+1 >= max || str[labelEnd+1] != ':' {
		return
	}

	for pos = labelEnd + 2; pos < max; pos++ {
		b := str[pos]
		if b == '\n' {
			lines++
		} else if b != ' ' {
			break
		}
	}

	href, endpos, ok := parseLinkDestination(str, pos, max)
	if !ok {
		return
	}
	href = normalizeLink(href)
	if !validateLink(href) {
		return
	}
	pos = endpos

	savedPos := pos
	savedLineNo := lines

	start := pos
	for ; pos < max; pos++ {
		b := str[pos]
		if b == '\n' {
			lines++
		} else if b != ' ' {
			break
		}
	}

	title, nlines, endpos, ok := parseLinkTitle(str, pos, max)
	if pos < max && start != pos && ok {
		pos = endpos
		lines += nlines
	} else {
		pos = savedPos
		lines = savedLineNo
	}

	for pos < max && str[pos] == ' ' {
		pos++
	}

	if pos < max && str[pos] != '\n' {
		return
	}

	label := normalizeReference(str[1:labelEnd])
	if label == "" {
		return false
	}

	if silent {
		return true
	}

	if s.Env.References == nil {
		s.Env.References = make(map[string]map[string]string)
	}
	if _, ok := s.Env.References[label]; !ok {
		s.Env.References[label] = map[string]string{
			"title": title,
			"href":  href,
		}
	}

	s.Line = startLine + lines + 1

	return true
}
<commit_msg>Fix reference edge case<commit_after>// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program.  If not, see <http://www.gnu.org/licenses/>.

package markdown

import "strings"

var referenceTerminatedBy []BlockRule

func ruleReference(s *StateBlock, startLine, _ int, silent bool) (_ bool) {
	pos := s.BMarks[startLine] + s.TShift[startLine]
	src := s.Src

	if src[pos] != '[' {
		return
	}

	pos++
	max := s.EMarks[startLine]

	for pos < max {
		if src[pos] == ']' && src[pos-1] != '\\' {
			if pos+1 == max {
				return
			}
			if src[pos+1] != ':' {
				return
			}
			break
		}
		pos++
	}

	nextLine := startLine + 1
	endLine := s.LineMax
outer:
	for ; nextLine < endLine && !s.IsLineEmpty(nextLine); nextLine++ {
		if s.TShift[nextLine]-s.BlkIndent > 3 {
			continue
		}

		for _, r := range referenceTerminatedBy {
			if r(s, nextLine, endLine, true) {
				break outer
			}
		}
	}

	str := strings.TrimSpace(s.Lines(startLine, nextLine, s.BlkIndent, false))
	max = len(str)
	lines := 0
	var labelEnd int
	for pos = 1; pos < max; pos++ {
		b := str[pos]
		if b == '[' {
			return
		} else if b == ']' {
			labelEnd = pos
			break
		} else if b == '\n' {
			lines++
		} else if b == '\\' {
			pos++
			if pos < max && str[pos] == '\n' {
				lines++
			}
		}
	}

	if labelEnd <= 0 || labelEnd+1 >= max || str[labelEnd+1] != ':' {
		return
	}

	for pos = labelEnd + 2; pos < max; pos++ {
		b := str[pos]
		if b == '\n' {
			lines++
		} else if b != ' ' {
			break
		}
	}

	href, endpos, ok := parseLinkDestination(str, pos, max)
	if !ok {
		return
	}
	href = normalizeLink(href)
	if !validateLink(href) {
		return
	}
	pos = endpos

	savedPos := pos
	savedLineNo := lines

	start := pos
	for ; pos < max; pos++ {
		b := str[pos]
		if b == '\n' {
			lines++
		} else if b != ' ' {
			break
		}
	}

	title, nlines, endpos, ok := parseLinkTitle(str, pos, max)
	if pos < max && start != pos && ok {
		pos = endpos
		lines += nlines
	} else {
		pos = savedPos
		lines = savedLineNo
	}

	for pos < max && str[pos] == ' ' {
		pos++
	}

	if pos < max && str[pos] != '\n' {
		if title != "" {
			title = ""
			pos = savedPos
			lines = savedLineNo
			for pos < max && src[pos] == ' ' {
				pos++
			}
		}
	}

	if pos < max && str[pos] != '\n' {
		return
	}

	label := normalizeReference(str[1:labelEnd])
	if label == "" {
		return false
	}

	if silent {
		return true
	}

	if s.Env.References == nil {
		s.Env.References = make(map[string]map[string]string)
	}
	if _, ok := s.Env.References[label]; !ok {
		s.Env.References[label] = map[string]string{
			"title": title,
			"href":  href,
		}
	}

	s.Line = startLine + lines + 1

	return true
}
<|endoftext|>


<commit_before>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package interfacer

import (
	"fmt"
	"go/ast"
	"go/token"
	"go/types"
	"os"
	"strings"

	"golang.org/x/tools/go/loader"
	"golang.org/x/tools/go/ssa"
	"golang.org/x/tools/go/ssa/ssautil"

	"github.com/kisielk/gotool"
	"github.com/mvdan/lint"
)

func toDiscard(usage *varUsage) bool {
	if usage.discard {
		return true
	}
	for to := range usage.assigned {
		if toDiscard(to) {
			return true
		}
	}
	return false
}

func allCalls(usage *varUsage, all, ftypes map[string]string) {
	for fname := range usage.calls {
		all[fname] = ftypes[fname]
	}
	for to := range usage.assigned {
		allCalls(to, all, ftypes)
	}
}

func (c *Checker) interfaceMatching(param *types.Var, usage *varUsage) (string, string) {
	if toDiscard(usage) {
		return "", ""
	}
	ftypes := typeFuncMap(param.Type())
	called := make(map[string]string, len(usage.calls))
	allCalls(usage, called, ftypes)
	s := funcMapString(called)
	return c.ifaces[s], s
}

type varUsage struct {
	calls   map[string]struct{}
	discard bool

	assigned map[*varUsage]struct{}
}

type funcDecl struct {
	astDecl *ast.FuncDecl
	ssaFn   *ssa.Function
}

// CheckArgs checks the packages specified by their import paths in
// args.
func CheckArgs(args []string) ([]string, error) {
	paths := gotool.ImportPaths(args)
	conf := loader.Config{}
	conf.AllowErrors = true
	rest, err := conf.FromArgs(paths, false)
	if err != nil {
		return nil, err
	}
	if len(rest) > 0 {
		return nil, fmt.Errorf("unwanted extra args: %v", rest)
	}
	lprog, err := conf.Load()
	if err != nil {
		return nil, err
	}
	prog := ssautil.CreateProgram(lprog, 0)
	prog.Build()
	issues, err := new(Checker).Check(lprog, prog)
	if err != nil {
		return nil, err
	}
	wd, err := os.Getwd()
	if err != nil {
		return nil, err
	}
	lines := make([]string, len(issues))
	for i, issue := range issues {
		fpos := prog.Fset.Position(issue.Pos()).String()
		if strings.HasPrefix(fpos, wd) {
			fpos = fpos[len(wd)+1:]
		}
		lines[i] = fmt.Sprintf("%s: %s", fpos, issue.Message())
	}
	return lines, nil
}

type Checker struct {
	lprog *loader.Program
	prog  *ssa.Program

	pkgTypes
	*loader.PackageInfo

	fset  *token.FileSet
	funcs []*funcDecl

	ssaByPos map[token.Pos]*ssa.Function

	discardFuncs map[*types.Signature]struct{}

	vars map[*types.Var]*varUsage
}

func (c *Checker) Check(lprog *loader.Program, prog *ssa.Program) ([]lint.Issue, error) {
	c.lprog, c.prog = lprog, prog
	var total []lint.Issue
	c.ssaByPos = make(map[token.Pos]*ssa.Function)
	for fn := range ssautil.AllFunctions(prog) {
		c.ssaByPos[fn.Pos()] = fn
	}
	for _, pinfo := range lprog.InitialPackages() {
		pkg := pinfo.Pkg
		c.getTypes(pkg)
		total = append(total, c.checkPkg(lprog.AllPackages[pkg])...)
	}
	return total, nil
}

func (c *Checker) checkPkg(info *loader.PackageInfo) []lint.Issue {
	c.PackageInfo = info
	c.discardFuncs = make(map[*types.Signature]struct{})
	c.vars = make(map[*types.Var]*varUsage)
	for _, f := range info.Files {
		ast.Walk(c, f)
	}
	return c.packageIssues()
}

func paramVarAndType(sign *types.Signature, i int) (*types.Var, types.Type) {
	params := sign.Params()
	extra := sign.Variadic() && i >= params.Len()-1
	if !extra {
		if i >= params.Len() {
			// builtins with multiple signatures
			return nil, nil
		}
		vr := params.At(i)
		return vr, vr.Type()
	}
	last := params.At(params.Len() - 1)
	switch x := last.Type().(type) {
	case *types.Slice:
		return nil, x.Elem()
	default:
		return nil, x
	}
}

func (c *Checker) varUsage(e ast.Expr) *varUsage {
	id, ok := e.(*ast.Ident)
	if !ok {
		return nil
	}
	param, ok := c.ObjectOf(id).(*types.Var)
	if !ok {
		// not a variable
		return nil
	}
	if usage, e := c.vars[param]; e {
		return usage
	}
	if !interesting(param.Type()) {
		return nil
	}
	usage := &varUsage{
		calls:    make(map[string]struct{}),
		assigned: make(map[*varUsage]struct{}),
	}
	c.vars[param] = usage
	return usage
}

func (c *Checker) addUsed(e ast.Expr, as types.Type) {
	if as == nil {
		return
	}
	if usage := c.varUsage(e); usage != nil {
		// using variable
		iface, ok := as.Underlying().(*types.Interface)
		if !ok {
			usage.discard = true
			return
		}
		for i := 0; i < iface.NumMethods(); i++ {
			m := iface.Method(i)
			usage.calls[m.Name()] = struct{}{}
		}
	} else if t, ok := c.TypeOf(e).(*types.Signature); ok {
		// using func
		c.discardFuncs[t] = struct{}{}
	}
}

func (c *Checker) addAssign(to, from ast.Expr) {
	pto := c.varUsage(to)
	pfrom := c.varUsage(from)
	if pto == nil || pfrom == nil {
		// either isn't interesting
		return
	}
	pfrom.assigned[pto] = struct{}{}
}

func (c *Checker) discard(e ast.Expr) {
	if usage := c.varUsage(e); usage != nil {
		usage.discard = true
	}
}

func (c *Checker) comparedWith(e, with ast.Expr) {
	if _, ok := with.(*ast.BasicLit); ok {
		c.discard(e)
	}
}

func (c *Checker) Visit(node ast.Node) ast.Visitor {
	var fd *funcDecl
	switch x := node.(type) {
	case *ast.FuncDecl:
		ssaFn := c.ssaByPos[x.Name.Pos()]
		if ssaFn == nil {
			return nil
		}
		fd = &funcDecl{
			astDecl: x,
			ssaFn:   ssaFn,
		}
		if c.funcSigns[signString(fd.ssaFn.Signature)] {
			// implements interface
			return nil
		}
	case *ast.SelectorExpr:
		if _, ok := c.TypeOf(x.Sel).(*types.Signature); !ok {
			c.discard(x.X)
		}
	case *ast.StarExpr:
		c.discard(x.X)
	case *ast.UnaryExpr:
		c.discard(x.X)
	case *ast.IndexExpr:
		c.discard(x.X)
	case *ast.IncDecStmt:
		c.discard(x.X)
	case *ast.BinaryExpr:
		switch x.Op {
		case token.EQL, token.NEQ:
			c.comparedWith(x.X, x.Y)
			c.comparedWith(x.Y, x.X)
		default:
			c.discard(x.X)
			c.discard(x.Y)
		}
	case *ast.ValueSpec:
		for _, val := range x.Values {
			c.addUsed(val, c.TypeOf(x.Type))
		}
	case *ast.AssignStmt:
		for i, val := range x.Rhs {
			left := x.Lhs[i]
			if x.Tok == token.ASSIGN {
				c.addUsed(val, c.TypeOf(left))
			}
			c.addAssign(left, val)
		}
	case *ast.CompositeLit:
		for i, e := range x.Elts {
			switch y := e.(type) {
			case *ast.KeyValueExpr:
				c.addUsed(y.Key, c.TypeOf(y.Value))
				c.addUsed(y.Value, c.TypeOf(y.Key))
			case *ast.Ident:
				c.addUsed(y, compositeIdentType(c.TypeOf(x), i))
			}
		}
	case *ast.CallExpr:
		switch y := c.TypeOf(x.Fun).Underlying().(type) {
		case *types.Signature:
			c.onMethodCall(x, y)
		default:
			// type conversion
			if len(x.Args) == 1 {
				c.addUsed(x.Args[0], y)
			}
		}
	}
	if fd != nil {
		c.funcs = append(c.funcs, fd)
	}
	return c
}

func compositeIdentType(t types.Type, i int) types.Type {
	switch x := t.(type) {
	case *types.Named:
		return compositeIdentType(x.Underlying(), i)
	case *types.Struct:
		return x.Field(i).Type()
	case *types.Array:
		return x.Elem()
	case *types.Slice:
		return x.Elem()
	}
	return nil
}

func (c *Checker) onMethodCall(ce *ast.CallExpr, sign *types.Signature) {
	for i, e := range ce.Args {
		paramObj, t := paramVarAndType(sign, i)
		// Don't if this is a parameter being re-used as itself
		// in a recursive call
		if id, ok := e.(*ast.Ident); ok {
			if paramObj == c.ObjectOf(id) {
				continue
			}
		}
		c.addUsed(e, t)
	}
	sel, ok := ce.Fun.(*ast.SelectorExpr)
	if !ok {
		return
	}
	// receiver func call on the left side
	if usage := c.varUsage(sel.X); usage != nil {
		usage.calls[sel.Sel.Name] = struct{}{}
	}
}

func (fd *funcDecl) paramGroups() [][]*types.Var {
	astList := fd.astDecl.Type.Params.List
	groups := make([][]*types.Var, len(astList))
	signIndex := 0
	for i, field := range astList {
		group := make([]*types.Var, len(field.Names))
		for j := range field.Names {
			group[j] = fd.ssaFn.Signature.Params().At(signIndex)
			signIndex++
		}
		groups[i] = group
	}
	return groups
}

func (c *Checker) packageIssues() []lint.Issue {
	var issues []lint.Issue
	for _, fd := range c.funcs {
		if _, e := c.discardFuncs[fd.ssaFn.Signature]; e {
			continue
		}
		for _, group := range fd.paramGroups() {
			issues = append(issues, c.groupIssues(fd, group)...)
		}
	}
	return issues
}

type Issue struct {
	pos token.Pos
	msg string
}

func (i Issue) Pos() token.Pos  { return i.pos }
func (i Issue) Message() string { return i.msg }

func (c *Checker) groupIssues(fd *funcDecl, group []*types.Var) []lint.Issue {
	var issues []lint.Issue
	for _, param := range group {
		usage := c.vars[param]
		if usage == nil {
			return nil
		}
		newType := c.paramNewType(fd.astDecl.Name.Name, param, usage)
		if newType == "" {
			return nil
		}
		issues = append(issues, Issue{
			pos: param.Pos(),
			msg: fmt.Sprintf("%s can be %s", param.Name(), newType),
		})
	}
	return issues
}

func willAddAllocation(t types.Type) bool {
	switch t.Underlying().(type) {
	case *types.Pointer, *types.Interface:
		return false
	}
	return true
}

func (c *Checker) paramNewType(funcName string, param *types.Var, usage *varUsage) string {
	t := param.Type()
	if !ast.IsExported(funcName) && willAddAllocation(t) {
		return ""
	}
	if named := typeNamed(t); named != nil {
		tname := named.Obj().Name()
		vname := param.Name()
		if mentionsName(funcName, tname) || mentionsName(funcName, vname) {
			return ""
		}
	}
	ifname, iftype := c.interfaceMatching(param, usage)
	if ifname == "" {
		return ""
	}
	if types.IsInterface(t.Underlying()) {
		if have := funcMapString(typeFuncMap(t)); have == iftype {
			return ""
		}
	}
	return ifname
}
<commit_msg>Collect all funcs at the start<commit_after>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package interfacer

import (
	"fmt"
	"go/ast"
	"go/token"
	"go/types"
	"os"
	"strings"

	"golang.org/x/tools/go/loader"
	"golang.org/x/tools/go/ssa"
	"golang.org/x/tools/go/ssa/ssautil"

	"github.com/kisielk/gotool"
	"github.com/mvdan/lint"
)

func toDiscard(usage *varUsage) bool {
	if usage.discard {
		return true
	}
	for to := range usage.assigned {
		if toDiscard(to) {
			return true
		}
	}
	return false
}

func allCalls(usage *varUsage, all, ftypes map[string]string) {
	for fname := range usage.calls {
		all[fname] = ftypes[fname]
	}
	for to := range usage.assigned {
		allCalls(to, all, ftypes)
	}
}

func (c *Checker) interfaceMatching(param *types.Var, usage *varUsage) (string, string) {
	if toDiscard(usage) {
		return "", ""
	}
	ftypes := typeFuncMap(param.Type())
	called := make(map[string]string, len(usage.calls))
	allCalls(usage, called, ftypes)
	s := funcMapString(called)
	return c.ifaces[s], s
}

type varUsage struct {
	calls   map[string]struct{}
	discard bool

	assigned map[*varUsage]struct{}
}

type funcDecl struct {
	astDecl *ast.FuncDecl
	ssaFn   *ssa.Function
}

// CheckArgs checks the packages specified by their import paths in
// args.
func CheckArgs(args []string) ([]string, error) {
	paths := gotool.ImportPaths(args)
	conf := loader.Config{}
	conf.AllowErrors = true
	rest, err := conf.FromArgs(paths, false)
	if err != nil {
		return nil, err
	}
	if len(rest) > 0 {
		return nil, fmt.Errorf("unwanted extra args: %v", rest)
	}
	lprog, err := conf.Load()
	if err != nil {
		return nil, err
	}
	prog := ssautil.CreateProgram(lprog, 0)
	prog.Build()
	issues, err := new(Checker).Check(lprog, prog)
	if err != nil {
		return nil, err
	}
	wd, err := os.Getwd()
	if err != nil {
		return nil, err
	}
	lines := make([]string, len(issues))
	for i, issue := range issues {
		fpos := prog.Fset.Position(issue.Pos()).String()
		if strings.HasPrefix(fpos, wd) {
			fpos = fpos[len(wd)+1:]
		}
		lines[i] = fmt.Sprintf("%s: %s", fpos, issue.Message())
	}
	return lines, nil
}

type Checker struct {
	lprog *loader.Program
	prog  *ssa.Program

	pkgTypes
	*loader.PackageInfo

	fset  *token.FileSet
	funcs []*funcDecl

	ssaByPos map[token.Pos]*ssa.Function

	discardFuncs map[*types.Signature]struct{}

	vars map[*types.Var]*varUsage
}

func (c *Checker) Check(lprog *loader.Program, prog *ssa.Program) ([]lint.Issue, error) {
	c.lprog, c.prog = lprog, prog
	var total []lint.Issue
	c.ssaByPos = make(map[token.Pos]*ssa.Function)
	wantPkg := make(map[*types.Package]bool)
	for _, pinfo := range lprog.InitialPackages() {
		wantPkg[pinfo.Pkg] = true
	}
	for fn := range ssautil.AllFunctions(prog) {
		if fn.Pkg == nil { // builtin?
			continue
		}
		if len(fn.Blocks) == 0 { // stub
			continue
		}
		if !wantPkg[fn.Pkg.Pkg] { // not part of given pkgs
			continue
		}
		c.ssaByPos[fn.Pos()] = fn
	}
	for _, pinfo := range lprog.InitialPackages() {
		pkg := pinfo.Pkg
		c.getTypes(pkg)
		total = append(total, c.checkPkg(lprog.AllPackages[pkg])...)
	}
	return total, nil
}

func (c *Checker) checkPkg(info *loader.PackageInfo) []lint.Issue {
	c.PackageInfo = info
	c.discardFuncs = make(map[*types.Signature]struct{})
	c.vars = make(map[*types.Var]*varUsage)
	for _, f := range info.Files {
		ast.Inspect(f, func(node ast.Node) bool {
			decl, ok := node.(*ast.FuncDecl)
			if !ok {
				return true
			}
			ssaFn := c.ssaByPos[decl.Name.Pos()]
			if ssaFn == nil {
				return true
			}
			fd := &funcDecl{
				astDecl: decl,
				ssaFn:   ssaFn,
			}
			if c.funcSigns[signString(fd.ssaFn.Signature)] {
				// implements interface
				return true
			}
			c.funcs = append(c.funcs, fd)
			return true
		})
		ast.Walk(c, f)
	}
	return c.packageIssues()
}

func paramVarAndType(sign *types.Signature, i int) (*types.Var, types.Type) {
	params := sign.Params()
	extra := sign.Variadic() && i >= params.Len()-1
	if !extra {
		if i >= params.Len() {
			// builtins with multiple signatures
			return nil, nil
		}
		vr := params.At(i)
		return vr, vr.Type()
	}
	last := params.At(params.Len() - 1)
	switch x := last.Type().(type) {
	case *types.Slice:
		return nil, x.Elem()
	default:
		return nil, x
	}
}

func (c *Checker) varUsage(e ast.Expr) *varUsage {
	id, ok := e.(*ast.Ident)
	if !ok {
		return nil
	}
	param, ok := c.ObjectOf(id).(*types.Var)
	if !ok {
		// not a variable
		return nil
	}
	if usage, e := c.vars[param]; e {
		return usage
	}
	if !interesting(param.Type()) {
		return nil
	}
	usage := &varUsage{
		calls:    make(map[string]struct{}),
		assigned: make(map[*varUsage]struct{}),
	}
	c.vars[param] = usage
	return usage
}

func (c *Checker) addUsed(e ast.Expr, as types.Type) {
	if as == nil {
		return
	}
	if usage := c.varUsage(e); usage != nil {
		// using variable
		iface, ok := as.Underlying().(*types.Interface)
		if !ok {
			usage.discard = true
			return
		}
		for i := 0; i < iface.NumMethods(); i++ {
			m := iface.Method(i)
			usage.calls[m.Name()] = struct{}{}
		}
	} else if t, ok := c.TypeOf(e).(*types.Signature); ok {
		// using func
		c.discardFuncs[t] = struct{}{}
	}
}

func (c *Checker) addAssign(to, from ast.Expr) {
	pto := c.varUsage(to)
	pfrom := c.varUsage(from)
	if pto == nil || pfrom == nil {
		// either isn't interesting
		return
	}
	pfrom.assigned[pto] = struct{}{}
}

func (c *Checker) discard(e ast.Expr) {
	if usage := c.varUsage(e); usage != nil {
		usage.discard = true
	}
}

func (c *Checker) comparedWith(e, with ast.Expr) {
	if _, ok := with.(*ast.BasicLit); ok {
		c.discard(e)
	}
}

func (c *Checker) Visit(node ast.Node) ast.Visitor {
	switch x := node.(type) {
	case *ast.SelectorExpr:
		if _, ok := c.TypeOf(x.Sel).(*types.Signature); !ok {
			c.discard(x.X)
		}
	case *ast.StarExpr:
		c.discard(x.X)
	case *ast.UnaryExpr:
		c.discard(x.X)
	case *ast.IndexExpr:
		c.discard(x.X)
	case *ast.IncDecStmt:
		c.discard(x.X)
	case *ast.BinaryExpr:
		switch x.Op {
		case token.EQL, token.NEQ:
			c.comparedWith(x.X, x.Y)
			c.comparedWith(x.Y, x.X)
		default:
			c.discard(x.X)
			c.discard(x.Y)
		}
	case *ast.ValueSpec:
		for _, val := range x.Values {
			c.addUsed(val, c.TypeOf(x.Type))
		}
	case *ast.AssignStmt:
		for i, val := range x.Rhs {
			left := x.Lhs[i]
			if x.Tok == token.ASSIGN {
				c.addUsed(val, c.TypeOf(left))
			}
			c.addAssign(left, val)
		}
	case *ast.CompositeLit:
		for i, e := range x.Elts {
			switch y := e.(type) {
			case *ast.KeyValueExpr:
				c.addUsed(y.Key, c.TypeOf(y.Value))
				c.addUsed(y.Value, c.TypeOf(y.Key))
			case *ast.Ident:
				c.addUsed(y, compositeIdentType(c.TypeOf(x), i))
			}
		}
	case *ast.CallExpr:
		switch y := c.TypeOf(x.Fun).Underlying().(type) {
		case *types.Signature:
			c.onMethodCall(x, y)
		default:
			// type conversion
			if len(x.Args) == 1 {
				c.addUsed(x.Args[0], y)
			}
		}
	}
	return c
}

func compositeIdentType(t types.Type, i int) types.Type {
	switch x := t.(type) {
	case *types.Named:
		return compositeIdentType(x.Underlying(), i)
	case *types.Struct:
		return x.Field(i).Type()
	case *types.Array:
		return x.Elem()
	case *types.Slice:
		return x.Elem()
	}
	return nil
}

func (c *Checker) onMethodCall(ce *ast.CallExpr, sign *types.Signature) {
	for i, e := range ce.Args {
		paramObj, t := paramVarAndType(sign, i)
		// Don't if this is a parameter being re-used as itself
		// in a recursive call
		if id, ok := e.(*ast.Ident); ok {
			if paramObj == c.ObjectOf(id) {
				continue
			}
		}
		c.addUsed(e, t)
	}
	sel, ok := ce.Fun.(*ast.SelectorExpr)
	if !ok {
		return
	}
	// receiver func call on the left side
	if usage := c.varUsage(sel.X); usage != nil {
		usage.calls[sel.Sel.Name] = struct{}{}
	}
}

func (fd *funcDecl) paramGroups() [][]*types.Var {
	astList := fd.astDecl.Type.Params.List
	groups := make([][]*types.Var, len(astList))
	signIndex := 0
	for i, field := range astList {
		group := make([]*types.Var, len(field.Names))
		for j := range field.Names {
			group[j] = fd.ssaFn.Signature.Params().At(signIndex)
			signIndex++
		}
		groups[i] = group
	}
	return groups
}

func (c *Checker) packageIssues() []lint.Issue {
	var issues []lint.Issue
	for _, fd := range c.funcs {
		if _, e := c.discardFuncs[fd.ssaFn.Signature]; e {
			continue
		}
		for _, group := range fd.paramGroups() {
			issues = append(issues, c.groupIssues(fd, group)...)
		}
	}
	return issues
}

type Issue struct {
	pos token.Pos
	msg string
}

func (i Issue) Pos() token.Pos  { return i.pos }
func (i Issue) Message() string { return i.msg }

func (c *Checker) groupIssues(fd *funcDecl, group []*types.Var) []lint.Issue {
	var issues []lint.Issue
	for _, param := range group {
		usage := c.vars[param]
		if usage == nil {
			return nil
		}
		newType := c.paramNewType(fd.astDecl.Name.Name, param, usage)
		if newType == "" {
			return nil
		}
		issues = append(issues, Issue{
			pos: param.Pos(),
			msg: fmt.Sprintf("%s can be %s", param.Name(), newType),
		})
	}
	return issues
}

func willAddAllocation(t types.Type) bool {
	switch t.Underlying().(type) {
	case *types.Pointer, *types.Interface:
		return false
	}
	return true
}

func (c *Checker) paramNewType(funcName string, param *types.Var, usage *varUsage) string {
	t := param.Type()
	if !ast.IsExported(funcName) && willAddAllocation(t) {
		return ""
	}
	if named := typeNamed(t); named != nil {
		tname := named.Obj().Name()
		vname := param.Name()
		if mentionsName(funcName, tname) || mentionsName(funcName, vname) {
			return ""
		}
	}
	ifname, iftype := c.interfaceMatching(param, usage)
	if ifname == "" {
		return ""
	}
	if types.IsInterface(t.Underlying()) {
		if have := funcMapString(typeFuncMap(t)); have == iftype {
			return ""
		}
	}
	return ifname
}
<|endoftext|>


<commit_before>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package interfacer

import (
	"fmt"
	"go/ast"
	"go/token"
	"go/types"
	"io"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"golang.org/x/tools/go/loader"
)

func toDiscard(vu *varUsage) bool {
	if vu.discard {
		return true
	}
	for to := range vu.assigned {
		if toDiscard(to) {
			return true
		}
	}
	return false
}

func (v *visitor) interfaceMatching(vr *types.Var, vu *varUsage) (string, string) {
	if toDiscard(vu) {
		return "", ""
	}
	allFuncs := typeFuncMap(vr.Type())
	if allFuncs == nil {
		return "", ""
	}
	called := make(map[string]string, len(vu.calls))
	for fname := range vu.calls {
		called[fname] = allFuncs[fname]
	}
	s := funcMapString(called)
	name := v.ifaceOf(s)
	if name == "" {
		return "", ""
	}
	return name, s
}

func orderedPkgs(prog *loader.Program) ([]*types.Package, error) {
	// InitialPackages() is not in the order that we passed to it
	// via Import() calls.
	// For now, make it deterministic by sorting import paths
	// alphabetically.
	unordered := prog.InitialPackages()
	paths := make([]string, 0, len(unordered))
	byPath := make(map[string]*types.Package, len(unordered))
	for _, info := range unordered {
		if info.Errors != nil {
			return nil, info.Errors[0]
		}
		path := info.Pkg.Path()
		paths = append(paths, path)
		byPath[path] = info.Pkg
	}
	sort.Sort(ByAlph(paths))
	pkgs := make([]*types.Package, 0, len(unordered))
	for _, path := range paths {
		pkgs = append(pkgs, byPath[path])
	}
	return pkgs, nil
}

// relPathErr makes converts errors by go/types and go/loader that use
// absolute paths into errors with relative paths
func relPathErr(err error, wd string) error {
	errStr := fmt.Sprintf("%v", err)
	if !strings.HasPrefix(errStr, "/") {
		return err
	}
	if strings.HasPrefix(errStr, wd) {
		return fmt.Errorf(errStr[len(wd)+1:])
	}
	return err
}

func CheckArgs(args []string, w io.Writer, verbose bool) error {
	wd, err := os.Getwd()
	if err != nil {
		return err
	}
	paths, err := recurse(args)
	if err != nil {
		return err
	}
	c := newCache()
	if _, err := c.FromArgs(paths, false); err != nil {
		return err
	}
	prog, err := c.Load()
	if err != nil {
		return err
	}
	pkgs, err := orderedPkgs(prog)
	if err != nil {
		return relPathErr(err, wd)
	}
	c.typesGet(pkgs)
	for _, pkg := range pkgs {
		info := prog.AllPackages[pkg]
		if verbose {
			fmt.Fprintln(w, info.Pkg.Path())
		}
		v := &visitor{
			cache:       c,
			PackageInfo: info,
			wd:          wd,
			w:           w,
			fset:        prog.Fset,
			vars:        make(map[*types.Var]*varUsage),
		}
		for _, f := range info.Files {
			ast.Walk(v, f)
		}
	}
	return nil
}

type varUsage struct {
	calls   map[string]struct{}
	discard bool

	assigned map[*varUsage]struct{}
}

type visitor struct {
	*cache
	*loader.PackageInfo

	wd    string
	w     io.Writer
	fset  *token.FileSet
	signs []*types.Signature
	warns [][]string
	level int

	vars map[*types.Var]*varUsage

	skipNext bool
}

func paramType(sign *types.Signature, i int) types.Type {
	params := sign.Params()
	extra := sign.Variadic() && i >= params.Len()-1
	if !extra {
		if i >= params.Len() {
			// builtins with multiple signatures
			return nil
		}
		return params.At(i).Type()
	}
	last := params.At(params.Len() - 1)
	switch x := last.Type().(type) {
	case *types.Slice:
		return x.Elem()
	default:
		return x
	}
}

func (v *visitor) varUsage(id *ast.Ident) *varUsage {
	vr, ok := v.ObjectOf(id).(*types.Var)
	if !ok {
		return nil
	}
	if vu, e := v.vars[vr]; e {
		return vu
	}
	vu := &varUsage{
		calls:    make(map[string]struct{}),
		assigned: make(map[*varUsage]struct{}),
	}
	v.vars[vr] = vu
	return vu
}

func (v *visitor) addUsed(id *ast.Ident, as types.Type) {
	if as == nil {
		return
	}
	vu := v.varUsage(id)
	if vu == nil {
		// not a variable
		return
	}
	iface, ok := as.Underlying().(*types.Interface)
	if !ok {
		vu.discard = true
		return
	}
	for i := 0; i < iface.NumMethods(); i++ {
		m := iface.Method(i)
		vu.calls[m.Name()] = struct{}{}
	}
}

func (v *visitor) addAssign(to, from *ast.Ident) {
	pto := v.varUsage(to)
	pfrom := v.varUsage(from)
	if pto == nil || pfrom == nil {
		// either isn't a variable
		return
	}
	pfrom.assigned[pto] = struct{}{}
}

func (v *visitor) discard(e ast.Expr) {
	id, ok := e.(*ast.Ident)
	if !ok {
		return
	}
	vu := v.varUsage(id)
	if vu == nil {
		// not a variable
		return
	}
	vu.discard = true
}

func (v *visitor) comparedWith(e ast.Expr, with ast.Expr) {
	if _, ok := with.(*ast.BasicLit); ok {
		v.discard(e)
	}
}

func (v *visitor) implementsIface(sign *types.Signature) bool {
	s := signString(sign)
	return v.funcOf(s) != ""
}

func (v *visitor) Visit(node ast.Node) ast.Visitor {
	if v.skipNext {
		v.skipNext = false
		return nil
	}
	var sign *types.Signature
	switch x := node.(type) {
	case *ast.FuncLit:
		sign = v.Types[x].Type.(*types.Signature)
		if v.implementsIface(sign) {
			return nil
		}
	case *ast.FuncDecl:
		sign = v.Defs[x.Name].Type().(*types.Signature)
		if v.implementsIface(sign) {
			return nil
		}
	case *ast.SelectorExpr:
		v.discard(x.X)
	case *ast.UnaryExpr:
		v.discard(x.X)
	case *ast.IndexExpr:
		v.discard(x.X)
	case *ast.IncDecStmt:
		v.discard(x.X)
	case *ast.BinaryExpr:
		v.onBinary(x)
	case *ast.AssignStmt:
		v.onAssign(x)
	case *ast.KeyValueExpr:
		v.onKeyValue(x)
	case *ast.CompositeLit:
		v.onComposite(x)
	case *ast.CallExpr:
		v.onCall(x)
	case nil:
		if top := v.signs[len(v.signs)-1]; top != nil {
			v.funcEnded(top)
		}
		v.signs = v.signs[:len(v.signs)-1]
	}
	if node != nil {
		v.signs = append(v.signs, sign)
		if sign != nil {
			v.level++
		}
	}
	return v
}

func (v *visitor) onBinary(be *ast.BinaryExpr) {
	switch be.Op {
	case token.EQL, token.NEQ:
	default:
		v.discard(be.X)
		v.discard(be.Y)
		return
	}
	v.comparedWith(be.X, be.Y)
	v.comparedWith(be.Y, be.X)
}

func (v *visitor) onAssign(as *ast.AssignStmt) {
	for i, e := range as.Rhs {
		id, ok := e.(*ast.Ident)
		if !ok {
			continue
		}
		left := as.Lhs[i]
		v.addUsed(id, v.Types[left].Type)
		if lid, ok := left.(*ast.Ident); ok {
			v.addAssign(lid, id)
		}
	}
}

func (v *visitor) onKeyValue(kv *ast.KeyValueExpr) {
	if id, ok := kv.Key.(*ast.Ident); ok {
		v.addUsed(id, v.TypeOf(kv.Value))
	}
	if id, ok := kv.Value.(*ast.Ident); ok {
		v.addUsed(id, v.TypeOf(kv.Key))
	}
}

func (v *visitor) onComposite(cl *ast.CompositeLit) {
	for _, e := range cl.Elts {
		if kv, ok := e.(*ast.KeyValueExpr); ok {
			v.onKeyValue(kv)
		}
	}
}

func (v *visitor) onCall(ce *ast.CallExpr) {
	if sign, ok := v.TypeOf(ce.Fun).(*types.Signature); ok {
		v.onMethodCall(ce, sign)
		return
	}
	if len(ce.Args) == 1 {
		v.discard(ce.Args[0])
	}
}

func (v *visitor) onMethodCall(ce *ast.CallExpr, sign *types.Signature) {
	switch y := ce.Fun.(type) {
	case *ast.Ident:
		v.skipNext = true
	case *ast.SelectorExpr:
		if _, ok := y.X.(*ast.Ident); ok {
			v.skipNext = true
		}
	}
	for i, e := range ce.Args {
		if id, ok := e.(*ast.Ident); ok {
			v.addUsed(id, paramType(sign, i))
		}
	}
	sel, ok := ce.Fun.(*ast.SelectorExpr)
	if !ok {
		return
	}
	left, ok := sel.X.(*ast.Ident)
	if !ok {
		return
	}
	vu := v.varUsage(left)
	if vu == nil {
		// not a variable
		return
	}
	vu.calls[sel.Sel.Name] = struct{}{}
}

func (v *visitor) funcEnded(sign *types.Signature) {
	v.level--
	v.warns = append(v.warns, v.funcWarns(sign))
	if v.level > 0 {
		return
	}
	for i := len(v.warns) - 1; i >= 0; i-- {
		warns := v.warns[i]
		for _, warn := range warns {
			fmt.Fprintln(v.w, warn)
		}
	}
	v.warns = nil
	v.vars = make(map[*types.Var]*varUsage)
}

func (v *visitor) funcWarns(sign *types.Signature) []string {
	var warns []string
	params := sign.Params()
	for i := 0; i < params.Len(); i++ {
		vr := params.At(i)
		vu := v.vars[vr]
		if vu == nil {
			continue
		}
		if warn := v.paramWarn(vr, vu); warn != "" {
			warns = append(warns, warn)
		}
	}
	return warns
}

func (v *visitor) paramWarn(vr *types.Var, vu *varUsage) string {
	ifname, iftype := v.interfaceMatching(vr, vu)
	if ifname == "" {
		return ""
	}
	t := vr.Type()
	if _, haveIface := t.Underlying().(*types.Interface); haveIface {
		if ifname == t.String() {
			return ""
		}
		have := funcMapString(typeFuncMap(t))
		if have == iftype {
			return ""
		}
	}
	pos := v.fset.Position(vr.Pos())
	fname := pos.Filename
	// go/loader seems to like absolute paths
	if rel, err := filepath.Rel(v.wd, fname); err == nil {
		fname = rel
	}
	pname := v.Pkg.Path()
	if strings.HasPrefix(ifname, pname+".") {
		ifname = ifname[len(pname)+1:]
	}
	return fmt.Sprintf("%s:%d:%d: %s can be %s",
		fname, pos.Line, pos.Column, vr.Name(), ifname)
}
<commit_msg>Fix typo in godoc<commit_after>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package interfacer

import (
	"fmt"
	"go/ast"
	"go/token"
	"go/types"
	"io"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"golang.org/x/tools/go/loader"
)

func toDiscard(vu *varUsage) bool {
	if vu.discard {
		return true
	}
	for to := range vu.assigned {
		if toDiscard(to) {
			return true
		}
	}
	return false
}

func (v *visitor) interfaceMatching(vr *types.Var, vu *varUsage) (string, string) {
	if toDiscard(vu) {
		return "", ""
	}
	allFuncs := typeFuncMap(vr.Type())
	if allFuncs == nil {
		return "", ""
	}
	called := make(map[string]string, len(vu.calls))
	for fname := range vu.calls {
		called[fname] = allFuncs[fname]
	}
	s := funcMapString(called)
	name := v.ifaceOf(s)
	if name == "" {
		return "", ""
	}
	return name, s
}

func orderedPkgs(prog *loader.Program) ([]*types.Package, error) {
	// InitialPackages() is not in the order that we passed to it
	// via Import() calls.
	// For now, make it deterministic by sorting import paths
	// alphabetically.
	unordered := prog.InitialPackages()
	paths := make([]string, 0, len(unordered))
	byPath := make(map[string]*types.Package, len(unordered))
	for _, info := range unordered {
		if info.Errors != nil {
			return nil, info.Errors[0]
		}
		path := info.Pkg.Path()
		paths = append(paths, path)
		byPath[path] = info.Pkg
	}
	sort.Sort(ByAlph(paths))
	pkgs := make([]*types.Package, 0, len(unordered))
	for _, path := range paths {
		pkgs = append(pkgs, byPath[path])
	}
	return pkgs, nil
}

// relPathErr converts errors by go/types and go/loader that use
// absolute paths into errors with relative paths
func relPathErr(err error, wd string) error {
	errStr := fmt.Sprintf("%v", err)
	if !strings.HasPrefix(errStr, "/") {
		return err
	}
	if strings.HasPrefix(errStr, wd) {
		return fmt.Errorf(errStr[len(wd)+1:])
	}
	return err
}

func CheckArgs(args []string, w io.Writer, verbose bool) error {
	wd, err := os.Getwd()
	if err != nil {
		return err
	}
	paths, err := recurse(args)
	if err != nil {
		return err
	}
	c := newCache()
	if _, err := c.FromArgs(paths, false); err != nil {
		return err
	}
	prog, err := c.Load()
	if err != nil {
		return err
	}
	pkgs, err := orderedPkgs(prog)
	if err != nil {
		return relPathErr(err, wd)
	}
	c.typesGet(pkgs)
	for _, pkg := range pkgs {
		info := prog.AllPackages[pkg]
		if verbose {
			fmt.Fprintln(w, info.Pkg.Path())
		}
		v := &visitor{
			cache:       c,
			PackageInfo: info,
			wd:          wd,
			w:           w,
			fset:        prog.Fset,
			vars:        make(map[*types.Var]*varUsage),
		}
		for _, f := range info.Files {
			ast.Walk(v, f)
		}
	}
	return nil
}

type varUsage struct {
	calls   map[string]struct{}
	discard bool

	assigned map[*varUsage]struct{}
}

type visitor struct {
	*cache
	*loader.PackageInfo

	wd    string
	w     io.Writer
	fset  *token.FileSet
	signs []*types.Signature
	warns [][]string
	level int

	vars map[*types.Var]*varUsage

	skipNext bool
}

func paramType(sign *types.Signature, i int) types.Type {
	params := sign.Params()
	extra := sign.Variadic() && i >= params.Len()-1
	if !extra {
		if i >= params.Len() {
			// builtins with multiple signatures
			return nil
		}
		return params.At(i).Type()
	}
	last := params.At(params.Len() - 1)
	switch x := last.Type().(type) {
	case *types.Slice:
		return x.Elem()
	default:
		return x
	}
}

func (v *visitor) varUsage(id *ast.Ident) *varUsage {
	vr, ok := v.ObjectOf(id).(*types.Var)
	if !ok {
		return nil
	}
	if vu, e := v.vars[vr]; e {
		return vu
	}
	vu := &varUsage{
		calls:    make(map[string]struct{}),
		assigned: make(map[*varUsage]struct{}),
	}
	v.vars[vr] = vu
	return vu
}

func (v *visitor) addUsed(id *ast.Ident, as types.Type) {
	if as == nil {
		return
	}
	vu := v.varUsage(id)
	if vu == nil {
		// not a variable
		return
	}
	iface, ok := as.Underlying().(*types.Interface)
	if !ok {
		vu.discard = true
		return
	}
	for i := 0; i < iface.NumMethods(); i++ {
		m := iface.Method(i)
		vu.calls[m.Name()] = struct{}{}
	}
}

func (v *visitor) addAssign(to, from *ast.Ident) {
	pto := v.varUsage(to)
	pfrom := v.varUsage(from)
	if pto == nil || pfrom == nil {
		// either isn't a variable
		return
	}
	pfrom.assigned[pto] = struct{}{}
}

func (v *visitor) discard(e ast.Expr) {
	id, ok := e.(*ast.Ident)
	if !ok {
		return
	}
	vu := v.varUsage(id)
	if vu == nil {
		// not a variable
		return
	}
	vu.discard = true
}

func (v *visitor) comparedWith(e ast.Expr, with ast.Expr) {
	if _, ok := with.(*ast.BasicLit); ok {
		v.discard(e)
	}
}

func (v *visitor) implementsIface(sign *types.Signature) bool {
	s := signString(sign)
	return v.funcOf(s) != ""
}

func (v *visitor) Visit(node ast.Node) ast.Visitor {
	if v.skipNext {
		v.skipNext = false
		return nil
	}
	var sign *types.Signature
	switch x := node.(type) {
	case *ast.FuncLit:
		sign = v.Types[x].Type.(*types.Signature)
		if v.implementsIface(sign) {
			return nil
		}
	case *ast.FuncDecl:
		sign = v.Defs[x.Name].Type().(*types.Signature)
		if v.implementsIface(sign) {
			return nil
		}
	case *ast.SelectorExpr:
		v.discard(x.X)
	case *ast.UnaryExpr:
		v.discard(x.X)
	case *ast.IndexExpr:
		v.discard(x.X)
	case *ast.IncDecStmt:
		v.discard(x.X)
	case *ast.BinaryExpr:
		v.onBinary(x)
	case *ast.AssignStmt:
		v.onAssign(x)
	case *ast.KeyValueExpr:
		v.onKeyValue(x)
	case *ast.CompositeLit:
		v.onComposite(x)
	case *ast.CallExpr:
		v.onCall(x)
	case nil:
		if top := v.signs[len(v.signs)-1]; top != nil {
			v.funcEnded(top)
		}
		v.signs = v.signs[:len(v.signs)-1]
	}
	if node != nil {
		v.signs = append(v.signs, sign)
		if sign != nil {
			v.level++
		}
	}
	return v
}

func (v *visitor) onBinary(be *ast.BinaryExpr) {
	switch be.Op {
	case token.EQL, token.NEQ:
	default:
		v.discard(be.X)
		v.discard(be.Y)
		return
	}
	v.comparedWith(be.X, be.Y)
	v.comparedWith(be.Y, be.X)
}

func (v *visitor) onAssign(as *ast.AssignStmt) {
	for i, e := range as.Rhs {
		id, ok := e.(*ast.Ident)
		if !ok {
			continue
		}
		left := as.Lhs[i]
		v.addUsed(id, v.Types[left].Type)
		if lid, ok := left.(*ast.Ident); ok {
			v.addAssign(lid, id)
		}
	}
}

func (v *visitor) onKeyValue(kv *ast.KeyValueExpr) {
	if id, ok := kv.Key.(*ast.Ident); ok {
		v.addUsed(id, v.TypeOf(kv.Value))
	}
	if id, ok := kv.Value.(*ast.Ident); ok {
		v.addUsed(id, v.TypeOf(kv.Key))
	}
}

func (v *visitor) onComposite(cl *ast.CompositeLit) {
	for _, e := range cl.Elts {
		if kv, ok := e.(*ast.KeyValueExpr); ok {
			v.onKeyValue(kv)
		}
	}
}

func (v *visitor) onCall(ce *ast.CallExpr) {
	if sign, ok := v.TypeOf(ce.Fun).(*types.Signature); ok {
		v.onMethodCall(ce, sign)
		return
	}
	if len(ce.Args) == 1 {
		v.discard(ce.Args[0])
	}
}

func (v *visitor) onMethodCall(ce *ast.CallExpr, sign *types.Signature) {
	switch y := ce.Fun.(type) {
	case *ast.Ident:
		v.skipNext = true
	case *ast.SelectorExpr:
		if _, ok := y.X.(*ast.Ident); ok {
			v.skipNext = true
		}
	}
	for i, e := range ce.Args {
		if id, ok := e.(*ast.Ident); ok {
			v.addUsed(id, paramType(sign, i))
		}
	}
	sel, ok := ce.Fun.(*ast.SelectorExpr)
	if !ok {
		return
	}
	left, ok := sel.X.(*ast.Ident)
	if !ok {
		return
	}
	vu := v.varUsage(left)
	if vu == nil {
		// not a variable
		return
	}
	vu.calls[sel.Sel.Name] = struct{}{}
}

func (v *visitor) funcEnded(sign *types.Signature) {
	v.level--
	v.warns = append(v.warns, v.funcWarns(sign))
	if v.level > 0 {
		return
	}
	for i := len(v.warns) - 1; i >= 0; i-- {
		warns := v.warns[i]
		for _, warn := range warns {
			fmt.Fprintln(v.w, warn)
		}
	}
	v.warns = nil
	v.vars = make(map[*types.Var]*varUsage)
}

func (v *visitor) funcWarns(sign *types.Signature) []string {
	var warns []string
	params := sign.Params()
	for i := 0; i < params.Len(); i++ {
		vr := params.At(i)
		vu := v.vars[vr]
		if vu == nil {
			continue
		}
		if warn := v.paramWarn(vr, vu); warn != "" {
			warns = append(warns, warn)
		}
	}
	return warns
}

func (v *visitor) paramWarn(vr *types.Var, vu *varUsage) string {
	ifname, iftype := v.interfaceMatching(vr, vu)
	if ifname == "" {
		return ""
	}
	t := vr.Type()
	if _, haveIface := t.Underlying().(*types.Interface); haveIface {
		if ifname == t.String() {
			return ""
		}
		have := funcMapString(typeFuncMap(t))
		if have == iftype {
			return ""
		}
	}
	pos := v.fset.Position(vr.Pos())
	fname := pos.Filename
	// go/loader seems to like absolute paths
	if rel, err := filepath.Rel(v.wd, fname); err == nil {
		fname = rel
	}
	pname := v.Pkg.Path()
	if strings.HasPrefix(ifname, pname+".") {
		ifname = ifname[len(pname)+1:]
	}
	return fmt.Sprintf("%s:%d:%d: %s can be %s",
		fname, pos.Line, pos.Column, vr.Name(), ifname)
}
<|endoftext|>


<commit_before>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package interfacer

import (
	"fmt"
	"go/ast"
	"go/token"
	"go/types"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"golang.org/x/tools/go/loader"
)

func toDiscard(usage *varUsage) bool {
	if usage.discard {
		return true
	}
	for to := range usage.assigned {
		if toDiscard(to) {
			return true
		}
	}
	return false
}

func (v *visitor) interfaceMatching(param *types.Var, usage *varUsage) (string, string) {
	if toDiscard(usage) {
		return "", ""
	}
	allFuncs := typeFuncMap(param.Type())
	if allFuncs == nil {
		return "", ""
	}
	called := make(map[string]string, len(usage.calls))
	for fname := range usage.calls {
		called[fname] = allFuncs[fname]
	}
	s := funcMapString(called)
	name := v.ifaceOf(s)
	if name == "" {
		return "", ""
	}
	return name, s
}

func progPackages(prog *loader.Program) ([]*types.Package, error) {
	// InitialPackages() is not in the order that we passed to it
	// via Import() calls.
	// For now, make it deterministic by sorting import paths
	// alphabetically.
	unordered := prog.InitialPackages()
	paths := make([]string, 0, len(unordered))
	for _, info := range unordered {
		if info.Errors != nil {
			return nil, info.Errors[0]
		}
		paths = append(paths, info.Pkg.Path())
	}
	sort.Sort(ByAlph(paths))
	pkgs := make([]*types.Package, 0, len(unordered))
	for _, path := range paths {
		pkgs = append(pkgs, prog.Package(path).Pkg)
	}
	return pkgs, nil
}

// relPathErr converts errors by go/types and go/loader that use
// absolute paths into errors with relative paths.
func relPathErr(err error, wd string) error {
	errStr := fmt.Sprintf("%v", err)
	if strings.HasPrefix(errStr, wd) {
		return fmt.Errorf(errStr[len(wd)+1:])
	}
	return err
}

// Warn is an interfacer warning suggesting a better type for a function
// parameter.
type Warn struct {
	// Position and name of the parameter
	Pos  token.Position
	Name string
	// New suggested type
	Type string
}

func (w Warn) String() string {
	return fmt.Sprintf("%s:%d:%d: %s can be %s",
		w.Pos.Filename, w.Pos.Line, w.Pos.Column, w.Name, w.Type)
}

// CheckArgs checks the packages specified by their import paths in
// args. If given an onPath function, it will call it as each package
// is checked. It will call the onWarn function as warnings are found.
// Returns an error, if any.
func CheckArgs(args []string, onPath func(string), onWarn func(Warn)) error {
	wd, err := os.Getwd()
	if err != nil {
		return err
	}
	paths, err := recurse(args)
	if err != nil {
		return err
	}
	c := newCache()
	rest, err := c.FromArgs(paths, false)
	if err != nil {
		return err
	}
	if len(rest) > 0 {
		return fmt.Errorf("unwanted extra args: %v", rest)
	}
	prog, err := c.Load()
	if err != nil {
		return err
	}
	pkgs, err := progPackages(prog)
	if err != nil {
		return relPathErr(err, wd)
	}
	c.typesGet(pkgs)
	v := &visitor{
		cache:  c,
		wd:     wd,
		fset:   prog.Fset,
		onWarn: onWarn,
	}
	for _, pkg := range pkgs {
		if onPath != nil {
			onPath(pkg.Path())
		}
		info := prog.AllPackages[pkg]
		v.PackageInfo = info
		v.vars = make(map[*types.Var]*varUsage)
		v.impNames = make(map[string]string)
		for _, f := range info.Files {
			for _, imp := range f.Imports {
				if imp.Name == nil {
					continue
				}
				name := imp.Name.Name
				path, err := strconv.Unquote(imp.Path.Value)
				if err != nil {
					return err
				}
				v.impNames[path] = name
			}
			ast.Walk(v, f)
		}
	}
	return nil
}

// CheckArgsOutput is like CheckArgs, but intended for human-readable
// text output.
func CheckArgsOutput(args []string, w io.Writer, verbose bool) error {
	onPath := func(path string) {
		if verbose {
			fmt.Fprintln(w, path)
		}
	}
	onWarn := func(warn Warn) {
		fmt.Fprintln(w, warn.String())
	}
	return CheckArgs(args, onPath, onWarn)
}

type varUsage struct {
	calls   map[string]struct{}
	discard bool

	assigned map[*varUsage]struct{}
}

type funcDecl struct {
	name string
	sign *types.Signature
}

type visitor struct {
	*cache
	*loader.PackageInfo

	wd     string
	fset   *token.FileSet
	funcs  []*funcDecl
	warns  [][]Warn
	onWarn func(Warn)
	level  int

	vars     map[*types.Var]*varUsage
	impNames map[string]string
}

func paramType(sign *types.Signature, i int) types.Type {
	params := sign.Params()
	extra := sign.Variadic() && i >= params.Len()-1
	if !extra {
		if i >= params.Len() {
			// builtins with multiple signatures
			return nil
		}
		return params.At(i).Type()
	}
	last := params.At(params.Len() - 1)
	switch x := last.Type().(type) {
	case *types.Slice:
		return x.Elem()
	default:
		return x
	}
}

func (v *visitor) varUsage(e ast.Expr) *varUsage {
	id, ok := e.(*ast.Ident)
	if !ok {
		return nil
	}
	param, ok := v.ObjectOf(id).(*types.Var)
	if !ok {
		return nil
	}
	if usage, e := v.vars[param]; e {
		return usage
	}
	usage := &varUsage{
		calls:    make(map[string]struct{}),
		assigned: make(map[*varUsage]struct{}),
	}
	v.vars[param] = usage
	return usage
}

func (v *visitor) addUsed(e ast.Expr, as types.Type) {
	if as == nil {
		return
	}
	usage := v.varUsage(e)
	if usage == nil {
		// not a variable
		return
	}
	iface, ok := as.Underlying().(*types.Interface)
	if !ok {
		usage.discard = true
		return
	}
	for i := 0; i < iface.NumMethods(); i++ {
		m := iface.Method(i)
		usage.calls[m.Name()] = struct{}{}
	}
}

func (v *visitor) addAssign(to, from ast.Expr) {
	pto := v.varUsage(to)
	pfrom := v.varUsage(from)
	if pto == nil || pfrom == nil {
		// either isn't a variable
		return
	}
	pfrom.assigned[pto] = struct{}{}
}

func (v *visitor) discard(e ast.Expr) {
	usage := v.varUsage(e)
	if usage == nil {
		// not a variable
		return
	}
	usage.discard = true
}

func (v *visitor) comparedWith(e, with ast.Expr) {
	if _, ok := with.(*ast.BasicLit); ok {
		v.discard(e)
	}
}

func (v *visitor) implementsIface(sign *types.Signature) bool {
	s := signString(sign)
	return v.funcOf(s) != ""
}

func (v *visitor) Visit(node ast.Node) ast.Visitor {
	var fd *funcDecl
	switch x := node.(type) {
	case *ast.FuncLit:
		fd = &funcDecl{
			sign: v.Types[x].Type.(*types.Signature),
		}
		if v.implementsIface(fd.sign) {
			return nil
		}
	case *ast.FuncDecl:
		fd = &funcDecl{
			sign: v.Defs[x.Name].Type().(*types.Signature),
			name: x.Name.Name,
		}
		if v.implementsIface(fd.sign) {
			return nil
		}
	case *ast.SelectorExpr:
		if _, ok := v.TypeOf(x.Sel).(*types.Signature); !ok {
			v.discard(x.X)
		}
	case *ast.UnaryExpr:
		v.discard(x.X)
	case *ast.IndexExpr:
		v.discard(x.X)
	case *ast.IncDecStmt:
		v.discard(x.X)
	case *ast.BinaryExpr:
		v.onBinary(x)
	case *ast.AssignStmt:
		v.onAssign(x)
	case *ast.CompositeLit:
		v.onComposite(x)
	case *ast.CallExpr:
		v.onCall(x)
	case nil:
		if top := v.funcs[len(v.funcs)-1]; top != nil {
			v.funcEnded(top)
		}
		v.funcs = v.funcs[:len(v.funcs)-1]
	}
	if node != nil {
		if fd != nil {
			v.level++
		}
		v.funcs = append(v.funcs, fd)
	}
	return v
}

func (v *visitor) onBinary(be *ast.BinaryExpr) {
	switch be.Op {
	case token.EQL, token.NEQ:
		v.comparedWith(be.X, be.Y)
		v.comparedWith(be.Y, be.X)
	default:
		v.discard(be.X)
		v.discard(be.Y)
	}
}

func (v *visitor) onAssign(as *ast.AssignStmt) {
	for i, val := range as.Rhs {
		left := as.Lhs[i]
		v.addUsed(val, v.Types[left].Type)
		v.addAssign(left, val)
	}
}

func (v *visitor) onKeyValue(kv *ast.KeyValueExpr) {
	v.addUsed(kv.Key, v.TypeOf(kv.Value))
	v.addUsed(kv.Value, v.TypeOf(kv.Key))
}

func compositeIdentType(t types.Type, i int) types.Type {
	switch x := t.(type) {
	case *types.Named:
		return compositeIdentType(x.Underlying(), i)
	case *types.Struct:
		return x.Field(i).Type()
	case *types.Array:
		return x.Elem()
	case *types.Slice:
		return x.Elem()
	default:
		return nil
	}
}

func (v *visitor) onComposite(cl *ast.CompositeLit) {
	for i, e := range cl.Elts {
		switch x := e.(type) {
		case *ast.KeyValueExpr:
			v.onKeyValue(x)
		case *ast.Ident:
			v.addUsed(x, compositeIdentType(v.TypeOf(cl), i))
		}
	}
}

func (v *visitor) onCall(ce *ast.CallExpr) {
	switch x := v.TypeOf(ce.Fun).Underlying().(type) {
	case *types.Signature:
		v.onMethodCall(ce, x)
	default:
		// type conversion
		if len(ce.Args) == 1 {
			v.addUsed(ce.Args[0], x.Underlying())
		}
	}
}

func (v *visitor) onMethodCall(ce *ast.CallExpr, sign *types.Signature) {
	for i, e := range ce.Args {
		v.addUsed(e, paramType(sign, i))
	}
	sel, ok := ce.Fun.(*ast.SelectorExpr)
	if !ok {
		return
	}
	usage := v.varUsage(sel.X)
	if usage == nil {
		// not a variable
		return
	}
	usage.calls[sel.Sel.Name] = struct{}{}
}

func (v *visitor) funcEnded(fd *funcDecl) {
	v.level--
	v.warns = append(v.warns, v.funcWarns(fd))
	if v.level > 0 {
		return
	}
	for i := len(v.warns) - 1; i >= 0; i-- {
		warns := v.warns[i]
		for _, warn := range warns {
			v.onWarn(warn)
		}
	}
	v.warns = nil
	v.vars = make(map[*types.Var]*varUsage)
}

func (v *visitor) funcWarns(fd *funcDecl) (warns []Warn) {
	params := fd.sign.Params()
	for i := 0; i < params.Len(); i++ {
		param := params.At(i)
		usage := v.vars[param]
		if usage == nil {
			continue
		}
		newType := v.paramNewType(fd.name, param, usage)
		if newType == "" {
			continue
		}
		pos := v.fset.Position(param.Pos())
		// go/loader seems to like absolute paths
		if rel, err := filepath.Rel(v.wd, pos.Filename); err == nil {
			pos.Filename = rel
		}
		warns = append(warns, Warn{pos, param.Name(), newType})
	}
	return
}

var fullPathParts = regexp.MustCompile(`^(\*)?(([^/]+/)*([^/]+)\.)?([^/]+)$`)

func (v *visitor) simpleName(fullName string) string {
	pname := v.Pkg.Path()
	if strings.HasPrefix(fullName, pname+".") {
		return fullName[len(pname)+1:]
	}
	ps := fullPathParts.FindStringSubmatch(fullName)
	fullPkg := strings.TrimSuffix(ps[2], ".")
	star := ps[1]
	pkg := ps[4]
	if name, e := v.impNames[fullPkg]; e {
		pkg = name
	}
	name := ps[5]
	return star + pkg + "." + name
}

func (v *visitor) paramNewType(funcName string, param *types.Var, usage *varUsage) string {
	t := param.Type()
	named := typeNamed(t)
	if named != nil {
		name := named.Obj().Name()
		if mentionsType(funcName, name) {
			return ""
		}
	}
	ifname, iftype := v.interfaceMatching(param, usage)
	if ifname == "" {
		return ""
	}
	if _, ok := t.Underlying().(*types.Interface); ok {
		if ifname == t.String() {
			return ""
		}
		if have := funcMapString(typeFuncMap(t)); have == iftype {
			return ""
		}
	}
	return v.simpleName(ifname)
}
<commit_msg>Split up package checking into separate func<commit_after>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package interfacer

import (
	"fmt"
	"go/ast"
	"go/token"
	"go/types"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"golang.org/x/tools/go/loader"
)

func toDiscard(usage *varUsage) bool {
	if usage.discard {
		return true
	}
	for to := range usage.assigned {
		if toDiscard(to) {
			return true
		}
	}
	return false
}

func (v *visitor) interfaceMatching(param *types.Var, usage *varUsage) (string, string) {
	if toDiscard(usage) {
		return "", ""
	}
	allFuncs := typeFuncMap(param.Type())
	if allFuncs == nil {
		return "", ""
	}
	called := make(map[string]string, len(usage.calls))
	for fname := range usage.calls {
		called[fname] = allFuncs[fname]
	}
	s := funcMapString(called)
	name := v.ifaceOf(s)
	if name == "" {
		return "", ""
	}
	return name, s
}

func progPackages(prog *loader.Program) ([]*types.Package, error) {
	// InitialPackages() is not in the order that we passed to it
	// via Import() calls.
	// For now, make it deterministic by sorting import paths
	// alphabetically.
	unordered := prog.InitialPackages()
	paths := make([]string, 0, len(unordered))
	for _, info := range unordered {
		if info.Errors != nil {
			return nil, info.Errors[0]
		}
		paths = append(paths, info.Pkg.Path())
	}
	sort.Sort(ByAlph(paths))
	pkgs := make([]*types.Package, 0, len(unordered))
	for _, path := range paths {
		pkgs = append(pkgs, prog.Package(path).Pkg)
	}
	return pkgs, nil
}

// relPathErr converts errors by go/types and go/loader that use
// absolute paths into errors with relative paths.
func relPathErr(err error, wd string) error {
	errStr := fmt.Sprintf("%v", err)
	if strings.HasPrefix(errStr, wd) {
		return fmt.Errorf(errStr[len(wd)+1:])
	}
	return err
}

// Warn is an interfacer warning suggesting a better type for a function
// parameter.
type Warn struct {
	// Position and name of the parameter
	Pos  token.Position
	Name string
	// New suggested type
	Type string
}

func (w Warn) String() string {
	return fmt.Sprintf("%s:%d:%d: %s can be %s",
		w.Pos.Filename, w.Pos.Line, w.Pos.Column, w.Name, w.Type)
}

type varUsage struct {
	calls   map[string]struct{}
	discard bool

	assigned map[*varUsage]struct{}
}

type funcDecl struct {
	name string
	sign *types.Signature
}

type visitor struct {
	*cache
	*loader.PackageInfo

	wd     string
	fset   *token.FileSet
	funcs  []*funcDecl
	warns  [][]Warn
	onWarn func(Warn)
	level  int

	vars     map[*types.Var]*varUsage
	impNames map[string]string
}

// CheckArgs checks the packages specified by their import paths in
// args. If given an onPath function, it will call it as each package
// is checked. It will call the onWarn function as warnings are found.
// Returns an error, if any.
func CheckArgs(args []string, onPath func(string), onWarn func(Warn)) error {
	wd, err := os.Getwd()
	if err != nil {
		return err
	}
	paths, err := recurse(args)
	if err != nil {
		return err
	}
	c := newCache()
	rest, err := c.FromArgs(paths, false)
	if err != nil {
		return err
	}
	if len(rest) > 0 {
		return fmt.Errorf("unwanted extra args: %v", rest)
	}
	prog, err := c.Load()
	if err != nil {
		return err
	}
	pkgs, err := progPackages(prog)
	if err != nil {
		return relPathErr(err, wd)
	}
	c.typesGet(pkgs)
	v := &visitor{
		cache:  c,
		wd:     wd,
		fset:   prog.Fset,
		onWarn: onWarn,
	}
	for _, pkg := range pkgs {
		if onPath != nil {
			onPath(pkg.Path())
		}
		info := prog.AllPackages[pkg]
		if err := v.checkPkg(info); err != nil {
			return err
		}
	}
	return nil
}

// CheckArgsOutput is like CheckArgs, but intended for human-readable
// text output.
func CheckArgsOutput(args []string, w io.Writer, verbose bool) error {
	onPath := func(path string) {
		if verbose {
			fmt.Fprintln(w, path)
		}
	}
	onWarn := func(warn Warn) {
		fmt.Fprintln(w, warn.String())
	}
	return CheckArgs(args, onPath, onWarn)
}

func (v *visitor) checkPkg(info *loader.PackageInfo) error {
	v.PackageInfo = info
	v.vars = make(map[*types.Var]*varUsage)
	v.impNames = make(map[string]string)
	for _, f := range info.Files {
		for _, imp := range f.Imports {
			if imp.Name == nil {
				continue
			}
			name := imp.Name.Name
			path, err := strconv.Unquote(imp.Path.Value)
			if err != nil {
				return err
			}
			v.impNames[path] = name
		}
		ast.Walk(v, f)
	}
	return nil
}

func paramType(sign *types.Signature, i int) types.Type {
	params := sign.Params()
	extra := sign.Variadic() && i >= params.Len()-1
	if !extra {
		if i >= params.Len() {
			// builtins with multiple signatures
			return nil
		}
		return params.At(i).Type()
	}
	last := params.At(params.Len() - 1)
	switch x := last.Type().(type) {
	case *types.Slice:
		return x.Elem()
	default:
		return x
	}
}

func (v *visitor) varUsage(e ast.Expr) *varUsage {
	id, ok := e.(*ast.Ident)
	if !ok {
		return nil
	}
	param, ok := v.ObjectOf(id).(*types.Var)
	if !ok {
		return nil
	}
	if usage, e := v.vars[param]; e {
		return usage
	}
	usage := &varUsage{
		calls:    make(map[string]struct{}),
		assigned: make(map[*varUsage]struct{}),
	}
	v.vars[param] = usage
	return usage
}

func (v *visitor) addUsed(e ast.Expr, as types.Type) {
	if as == nil {
		return
	}
	usage := v.varUsage(e)
	if usage == nil {
		// not a variable
		return
	}
	iface, ok := as.Underlying().(*types.Interface)
	if !ok {
		usage.discard = true
		return
	}
	for i := 0; i < iface.NumMethods(); i++ {
		m := iface.Method(i)
		usage.calls[m.Name()] = struct{}{}
	}
}

func (v *visitor) addAssign(to, from ast.Expr) {
	pto := v.varUsage(to)
	pfrom := v.varUsage(from)
	if pto == nil || pfrom == nil {
		// either isn't a variable
		return
	}
	pfrom.assigned[pto] = struct{}{}
}

func (v *visitor) discard(e ast.Expr) {
	usage := v.varUsage(e)
	if usage == nil {
		// not a variable
		return
	}
	usage.discard = true
}

func (v *visitor) comparedWith(e, with ast.Expr) {
	if _, ok := with.(*ast.BasicLit); ok {
		v.discard(e)
	}
}

func (v *visitor) implementsIface(sign *types.Signature) bool {
	s := signString(sign)
	return v.funcOf(s) != ""
}

func (v *visitor) Visit(node ast.Node) ast.Visitor {
	var fd *funcDecl
	switch x := node.(type) {
	case *ast.FuncLit:
		fd = &funcDecl{
			sign: v.Types[x].Type.(*types.Signature),
		}
		if v.implementsIface(fd.sign) {
			return nil
		}
	case *ast.FuncDecl:
		fd = &funcDecl{
			sign: v.Defs[x.Name].Type().(*types.Signature),
			name: x.Name.Name,
		}
		if v.implementsIface(fd.sign) {
			return nil
		}
	case *ast.SelectorExpr:
		if _, ok := v.TypeOf(x.Sel).(*types.Signature); !ok {
			v.discard(x.X)
		}
	case *ast.UnaryExpr:
		v.discard(x.X)
	case *ast.IndexExpr:
		v.discard(x.X)
	case *ast.IncDecStmt:
		v.discard(x.X)
	case *ast.BinaryExpr:
		v.onBinary(x)
	case *ast.AssignStmt:
		v.onAssign(x)
	case *ast.CompositeLit:
		v.onComposite(x)
	case *ast.CallExpr:
		v.onCall(x)
	case nil:
		if top := v.funcs[len(v.funcs)-1]; top != nil {
			v.funcEnded(top)
		}
		v.funcs = v.funcs[:len(v.funcs)-1]
	}
	if node != nil {
		if fd != nil {
			v.level++
		}
		v.funcs = append(v.funcs, fd)
	}
	return v
}

func (v *visitor) onBinary(be *ast.BinaryExpr) {
	switch be.Op {
	case token.EQL, token.NEQ:
		v.comparedWith(be.X, be.Y)
		v.comparedWith(be.Y, be.X)
	default:
		v.discard(be.X)
		v.discard(be.Y)
	}
}

func (v *visitor) onAssign(as *ast.AssignStmt) {
	for i, val := range as.Rhs {
		left := as.Lhs[i]
		v.addUsed(val, v.Types[left].Type)
		v.addAssign(left, val)
	}
}

func (v *visitor) onKeyValue(kv *ast.KeyValueExpr) {
	v.addUsed(kv.Key, v.TypeOf(kv.Value))
	v.addUsed(kv.Value, v.TypeOf(kv.Key))
}

func compositeIdentType(t types.Type, i int) types.Type {
	switch x := t.(type) {
	case *types.Named:
		return compositeIdentType(x.Underlying(), i)
	case *types.Struct:
		return x.Field(i).Type()
	case *types.Array:
		return x.Elem()
	case *types.Slice:
		return x.Elem()
	default:
		return nil
	}
}

func (v *visitor) onComposite(cl *ast.CompositeLit) {
	for i, e := range cl.Elts {
		switch x := e.(type) {
		case *ast.KeyValueExpr:
			v.onKeyValue(x)
		case *ast.Ident:
			v.addUsed(x, compositeIdentType(v.TypeOf(cl), i))
		}
	}
}

func (v *visitor) onCall(ce *ast.CallExpr) {
	switch x := v.TypeOf(ce.Fun).Underlying().(type) {
	case *types.Signature:
		v.onMethodCall(ce, x)
	default:
		// type conversion
		if len(ce.Args) == 1 {
			v.addUsed(ce.Args[0], x.Underlying())
		}
	}
}

func (v *visitor) onMethodCall(ce *ast.CallExpr, sign *types.Signature) {
	for i, e := range ce.Args {
		v.addUsed(e, paramType(sign, i))
	}
	sel, ok := ce.Fun.(*ast.SelectorExpr)
	if !ok {
		return
	}
	usage := v.varUsage(sel.X)
	if usage == nil {
		// not a variable
		return
	}
	usage.calls[sel.Sel.Name] = struct{}{}
}

func (v *visitor) funcEnded(fd *funcDecl) {
	v.level--
	v.warns = append(v.warns, v.funcWarns(fd))
	if v.level > 0 {
		return
	}
	for i := len(v.warns) - 1; i >= 0; i-- {
		warns := v.warns[i]
		for _, warn := range warns {
			v.onWarn(warn)
		}
	}
	v.warns = nil
	v.vars = make(map[*types.Var]*varUsage)
}

func (v *visitor) funcWarns(fd *funcDecl) (warns []Warn) {
	params := fd.sign.Params()
	for i := 0; i < params.Len(); i++ {
		param := params.At(i)
		usage := v.vars[param]
		if usage == nil {
			continue
		}
		newType := v.paramNewType(fd.name, param, usage)
		if newType == "" {
			continue
		}
		pos := v.fset.Position(param.Pos())
		// go/loader seems to like absolute paths
		if rel, err := filepath.Rel(v.wd, pos.Filename); err == nil {
			pos.Filename = rel
		}
		warns = append(warns, Warn{pos, param.Name(), newType})
	}
	return
}

var fullPathParts = regexp.MustCompile(`^(\*)?(([^/]+/)*([^/]+)\.)?([^/]+)$`)

func (v *visitor) simpleName(fullName string) string {
	pname := v.Pkg.Path()
	if strings.HasPrefix(fullName, pname+".") {
		return fullName[len(pname)+1:]
	}
	ps := fullPathParts.FindStringSubmatch(fullName)
	fullPkg := strings.TrimSuffix(ps[2], ".")
	star := ps[1]
	pkg := ps[4]
	if name, e := v.impNames[fullPkg]; e {
		pkg = name
	}
	name := ps[5]
	return star + pkg + "." + name
}

func (v *visitor) paramNewType(funcName string, param *types.Var, usage *varUsage) string {
	t := param.Type()
	named := typeNamed(t)
	if named != nil {
		name := named.Obj().Name()
		if mentionsType(funcName, name) {
			return ""
		}
	}
	ifname, iftype := v.interfaceMatching(param, usage)
	if ifname == "" {
		return ""
	}
	if _, ok := t.Underlying().(*types.Interface); ok {
		if ifname == t.String() {
			return ""
		}
		if have := funcMapString(typeFuncMap(t)); have == iftype {
			return ""
		}
	}
	return v.simpleName(ifname)
}
<|endoftext|>