gocapsule
A Go linter that enforces encapsulation by preventing direct struct creation, type conversion, and field reassignment when New** constructors exist.
Features
- Prevent direct struct literal creation: If a package has a
NewXxx constructor, external packages cannot create the struct directly using struct literals
- Prevent direct type conversion: For defined types (e.g.,
type Email string) with constructors, external packages cannot use direct type conversions
- Prevent field reassignment: External packages cannot reassign public fields of structs that have constructors
- Embedded field support: Detects violations through embedded field access (e.g.,
container.User.Name = "x")
Installation
go install github.com/YuitoSato/gocapsule@latest
Usage
Standalone
gocapsule ./...
With Flags
Ignore Specific Packages
Use the -ignorePackages flag to exclude specific packages from analysis. This is useful for ignoring standard library packages like net/http that have constructors but are used in legitimate ways.
gocapsule -ignorePackages="net/http,database/sql" ./...
With golangci-lint
- Create
.custom-gcl.yml:
version: v2.7.2
plugins:
- module: 'github.com/YuitoSato/gocapsule'
import: 'github.com/YuitoSato/gocapsule/gocapsule'
version: v0.2.0
- Add to
.golangci.yml:
linters:
enable:
- gocapsule
settings:
custom:
gocapsule:
type: "module"
- Build and run:
golangci-lint custom
./custom-gcl run ./...
Example
Structs
Given a package with a constructor:
// package user
type User struct {
Name string
Email string
}
func NewUser(name, email string) *User {
return &User{Name: name, Email: email}
}
The following code in an external package will be flagged:
// package main
import "user"
func main() {
// NG: direct struct literal creation
u := &user.User{Name: "test"}
// -> "direct struct literal creation of User is not allowed; use user.NewUser() instead"
// OK: using constructor
u := user.NewUser("test", "test@example.com")
// NG: field reassignment
u.Name = "modified"
// -> "direct field assignment to User.Name is not allowed; User has a constructor NewUser()"
}
Defined Types
Defined types with constructors are also protected:
// package email
type Email string
func NewEmail(s string) (Email, error) {
// validate email format
return Email(s), nil
}
// package main
import "email"
func main() {
// NG: direct type conversion
e := email.Email("test@example.com")
// -> "direct type conversion to Email is not allowed; use email.NewEmail() instead"
// OK: using constructor
e, err := email.NewEmail("test@example.com")
}
Rules
- Constructor pattern: Functions matching
New[A-Z]* that return *TypeName or TypeName
- Same package allowed: Code within the same package can freely create types and modify fields
- No constructor = no restriction: Types without
New** constructors have no restrictions
- Supported types: Both structs and defined types (e.g.,
type Email string) are supported
License
MIT