Skip to content
Merged
1 change: 1 addition & 0 deletions _tools/customlint/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func (f *plugin) BuildAnalyzers() ([]*analysis.Analyzer, error) {
return []*analysis.Analyzer{
emptyCaseAnalyzer,
shadowAnalyzer,
unexportedAPIAnalyzer,
}, nil
}

Expand Down
4 changes: 4 additions & 0 deletions _tools/customlint/testdata/shadow/shadow.go.golden
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,17 @@
type isType int

func F5() isType {
~
!!! unexportedapi: exported API references unexported identifier isType
var isType isType // OK
return isType
}

type isAlias int

func F6() isAlias {
~
!!! unexportedapi: exported API references unexported identifier isAlias
var isAlias isAlias // OK
return isAlias
}
Expand Down
211 changes: 211 additions & 0 deletions _tools/customlint/testdata/unexportedapi/unexportedapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package unexportedapi

type Foo struct {
Bar *oops
}

type oops struct {
v int
}

type Okay struct {
Sure int
Value ***Okay2
}

type Okay2 struct {
VeryGood struct{}
}

func OkayFunc(v *Okay) *Okay2 {
if v == nil {
return nil
}
return **v.Value
}

// Test cases for various scenarios

// Exported function with unexported parameter type
func BadFunc(x unexported) {}

// Exported function with unexported return type
func AnotherBadFunc() *unexported {
return nil
}

// Exported function with unexported type in slice
func SliceFunc(x []unexported) {}

// Exported function with unexported type in map
func MapFunc(x map[string]unexported) {}

// Exported function with unexported type in map key
func MapKeyFunc(x map[unexported]string) {}

// Exported function with unexported type in channel
func ChanFunc(x chan unexported) {}

// Exported type alias to unexported type
type BadAlias = unexported

// Exported type with unexported embedded field (OK since unexported has no exported members)
type OkayEmbed struct {
unexported
}

// Unexported type with exported field
type unexportedWithExportedField struct {
ExportedField int
}

// Bad - exported type embedding unexported type with exported members
type BadEmbed struct {
unexportedWithExportedField
}

// Unexported type - should not trigger
type okayUnexported struct {
field unexported
}

// Exported interface with unexported type in method
type BadInterface interface {
Method(x unexported)
}

// Exported interface with unexported return type
type AnotherBadInterface interface {
Method() unexported
}

type unexported struct {
x int
}

// Exported function with multiple return values including unexported
func MultiReturn() (int, unexported, error) {
return 0, unexported{}, nil
}

// Exported variable with unexported type
var BadVar unexported

// Exported const with unexported type (should not be possible, but let's be safe)
// const BadConst unexported = unexported{} // This won't compile anyway

// Array of unexported type
type BadArray [10]unexported

// Exported function with variadic unexported parameter
func VariadicFunc(args ...unexported) {}

// Exported type with method returning unexported type
type ExportedWithMethod struct{}

func (e ExportedWithMethod) Method() unexported {
return unexported{}
}

// Exported type with pointer receiver method returning unexported type
func (e *ExportedWithMethod) PointerMethod() *unexported {
return nil
}

// Generic type with unexported type constraint (Go 1.18+)
type GenericExported[T any] struct {
Value T
}

// Okay - unexported method on exported type (methods are not part of exported API unless on exported interface)
func (e ExportedWithMethod) unexportedMethod() unexported {
return unexported{}
}

// Test variables initialized with function calls

// Helper functions for testing
func helperReturnsExported() *Okay2 {
return &Okay2{}
}

func helperReturnsUnexported() unexported {
return unexported{}
}

// Okay - exported variable initialized by calling unexported function that returns exported type
var OkayVarFromUnexportedFunc = helperReturnsExported()

// Bad - exported variable initialized by calling exported function that returns unexported type
var BadVarFromFunc = helperReturnsUnexported()

// Okay - exported variable with explicit type (implementation doesn't matter)
var OkayVarExplicitType *Okay2 = helperReturnsExported()

// Bad - exported variable with explicit unexported type
var BadVarExplicitType unexported = helperReturnsUnexported()

// Test type aliases
type (
ExportedString string
unexportedString string
)

// Okay - exported function using exported type alias
func OkayTypeAlias(s ExportedString) {}

// Bad - exported function using unexported type alias
func BadTypeAlias(s unexportedString) {}

// Test unexported types with exported methods (for interface satisfaction)
type unexportedImpl struct {
value int
}

// Okay - exported method on unexported type (not part of public API, often used for interface satisfaction)
func (u *unexportedImpl) ExportedMethod() int {
return u.value
}

// Okay - exported method on unexported type can return unexported types
func (u *unexportedImpl) AnotherMethod() unexported {
return unexported{}
}

// Test for avoiding duplicate errors on embedded types with methods

type BaseWithBadMethod struct{}

// This method has an unexported return type - should be flagged once
func (b *BaseWithBadMethod) GetUnexported() *unexported {
return nil
}

// This type embeds BaseWithBadMethod - should NOT re-report the GetUnexported method issue
type DerivedEmbedding struct {
BaseWithBadMethod
}

// Test embedding unexported type with exported method that references unexported type
type unexportedBase struct{}

// This exported method on unexported type won't be checked (unexported type methods are skipped)
func (u *unexportedBase) MethodWithBadReturn() *unexported {
return nil
}

// This embeds an unexported type - what happens?
// OK because methods on unexported types aren't checked
type EmbeddingUnexportedBase struct {
unexportedBase
}

// Test embedding unexported type with exported field that references unexported type
type unexportedBaseWithField struct {
ExportedField *unexported
}

// Bad - embeds unexported type with exported field that references unexported type
type EmbeddingUnexportedBaseWithField struct {
unexportedBaseWithField
}
Loading
Loading