Help us shape the future of Go! Take the 2026 Go Developer Survey

goseal

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Dec 30, 2025 License: MIT Imports: 9 Imported by: 0

README

goseal

goseal is a linter that checks direct initialization of Go structs and enforces creation through factory functions. It "seals" your structs, ensuring they can only be instantiated through approved factory functions. Use it to prevent invalid values from leaking into your objects, such as when applying the Value Object pattern.

Installation

go install github.com/jimmysharp/goseal/cmd/goseal@latest

Configuration

Create .goseal.yml in your project root:

Note: While target-packages is optional (omitting it will target all packages), it is strongly recommended to explicitly specify the packages containing your domain structs. This prevents false positives with structs in third-party or standard library code.

# List of regexps for packages containing target structs to protect
# If not specified or empty, all packages are targeted
# Default: []
target-packages:
  - "github\\.com/yourorg/domain/.*"
  - "github\\.com/yourorg/model/.*"

# List of regexps for struct names to exclude from protection
# Even if in target-packages, these structs won't be protected
# Default: []
exclude-structs:
  - "^Config$"
  - "DTO$"

# List of regexps for functions considered as factory functions
# If empty, all function names are allowed
# Default: []
factory-names:
  - "^New.*"

# Scope for struct initialization
# - any: Allow initialization from all packages
# - in-target-packages: Allow initialization from packages in target-packages
# - same-package: Allow initialization only within the same package
# Default: same-package
init-scope: same-package

# Scope for field mutation
# - any: Allow field mutation everywhere
# - receiver: Allow field mutation only in receiver methods
# - same-package: Allow field mutation within the same package
# - never: Never allow field mutation
# Default: receiver
mutation-scope: receiver

# List of regexps for files to ignore
# Default: []
ignore-files:
  - "_test\\.go$"
  - "mock_.*\\.go$"

Usage

goseal ./...

Examples

Example domain object
package domain

type User struct {
    ID   string
    Name string
}

func NewUser(id, name string) *User {
    // ✅ Allowed inside a factory function
    return &User{
        ID:   id,
        Name: name,
    }
}

// ✅ Allowed: mutation in receiver method (when mutation-scope: receiver)
func (u *User) UpdateName(name string) {
    u.Name = name
}
Example that gets flagged (NG)
package app

import "github.com/yourorg/domain"

func CreateUser() {
    // ❌ Direct use of a struct literal (when init-scope: same-package)
    user := domain.User{
        ID:   "123",
        Name: "Alice",
    }
    
    // ❌ Direct field assignment (when mutation-scope: receiver)
    user.Name = "Bob"
}
package app

import "github.com/yourorg/domain"

func CreateUser() {
    // ✅ Use the factory function
    user := domain.NewUser("123", "Alice")
    
    // ✅ Use the method for mutation
    user.UpdateName("Bob")
}

Usecases

Strict constructor usage with immutability

Force constructor usage in domain packages and prohibit all mutations:

target-packages:
  - "github\\.com/yourorg/domain/.*"
factory-names:
  - "^New.*"
init-scope: same-package
mutation-scope: never
Entity Pattern

Allow mutation through methods while preventing direct field access:

target-packages:
  - "github\\.com/yourorg/domain/entity/.*"
factory-names:
  - "^New.*"
init-scope: same-package
mutation-scope: receiver
Domain packages with subpackages

Allow cross-package initialization within domain packages (useful when domain is split into subpackages like domain/user, domain/order, etc.):

target-packages:
  - "github\\.com/yourorg/domain/.*"
factory-names:
  - "^New.*"
init-scope: in-target-packages
mutation-scope: never
Trust package boundaries

Allow free operations within domain packages:

target-packages:
  - "github\\.com/yourorg/domain/.*"
init-scope: same-package
mutation-scope: same-package
Exclude specific structs

Exclude specific structs (e.g., configuration structs, DTOs) from protection:

target-packages:
  - "github\\.com/yourorg/domain/.*"
exclude-structs:
  - "^Config$"
  - "DTO$"
factory-names:
  - "^New.*"
init-scope: same-package
mutation-scope: receiver

License

MIT License – see LICENSE for details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewAnalyzer

func NewAnalyzer(config *Config) *analysis.Analyzer

Types

type Config

type Config struct {
	TargetPackages []*regexp.Regexp // Regex patterns for packages containing target structs (if empty, all packages are targeted)
	ExcludeStructs []*regexp.Regexp // Regex patterns for struct names to exclude from protection
	FactoryNames   []*regexp.Regexp // Regex patterns for factory function names (if empty, all function names are allowed)
	InitScope      InitScope        // Scope for struct initialization
	MutationScope  MutationScope    // Scope for field mutation
	IgnoreFiles    []*regexp.Regexp // Regex patterns for files to ignore
}

func NewConfig

func NewConfig(
	targetPackages []*regexp.Regexp,
	excludeStructs []*regexp.Regexp,
	factoryNames []*regexp.Regexp,
	initScope InitScope,
	mutationScope MutationScope,
	ignoreFiles []*regexp.Regexp,
) (*Config, error)

func ParseConfig

func ParseConfig(path string) (*Config, error)

type InitScope

type InitScope string
const (
	InitScopeAny              InitScope = "any"
	InitScopeInTargetPackages InitScope = "in-target-packages"
	InitScopeSamePackage      InitScope = "same-package"
)

type MutationScope

type MutationScope string
const (
	MutationScopeAny         MutationScope = "any"
	MutationScopeReceiver    MutationScope = "receiver"
	MutationScopeSamePackage MutationScope = "same-package"
	MutationScopeNever       MutationScope = "never"
)

Directories

Path Synopsis
cmd
goseal command

Jump to

Keyboard shortcuts

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