vischeck

package module
v0.0.0-...-a23c8fb Latest Latest
Warning

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

Go to latest
Published: Mar 12, 2023 License: Unlicense Imports: 6 Imported by: 0

README

vischeck

A simple linter which checks certain visibility rules have been adhered to.

Usage

Visibility rules are defined as tags on struct fields. For example:

package main

type MyStruct struct {
	Name string `visibility:"readonly"`
	Age  int    `visibility:"readonly"`
}

func main() {
	s := MyStruct{}

	s.Name = "Frank"  // misuse of readonly field: cannot assign
	_ = &s.Name       // misuse of readonly field: cannot take address
	s.Age++           // misuse of readonly field: cannot increment
}
Available Rules

The available rules are as follows:

readonly

Checks fields marked as readonly are only mutated by receiver functions defined on their containing type.

Limitations
  • Fields stored as a pointer type cannot be checked, instead vischeck will emit an error indicating that the field type is invalid.
  • Structs embedding structs with visibility rules defined can freely modify the embedded fields.
  • Struct literals are not yet checked.

Rationale

In order to simplify (un)marshalling structs, it's often convenient to export fields. Typically it is safe to read these fields but setting them may require using a specific API in order to prevent invalid states. Ensuring the correct APIs are used to set these fields is usually the responsibility of the developer and code reviewer - this is where vischeck comes in. With vischeck, developers will get a linting error if they attempt to mutate certain struct fields directly, forcing them to refactor their code to use the correct API. This linter would be disabled where direct write access to fields is required (for example in some data access layer).

package models

import (
	"errors"
	"fmt"
)

type Order struct {
	// The visibility tag ensures this field is never manipulated directly,
	// forcing developers to use the `SetStatus` method, instead.
	Status OrderStatus `visibility:"readonly"`
}

var ErrInvalidOrderStatus = errors.New("invalid status")

func (order *Order) SetStatus(status OrderStatus) error {
	if !status.Valid() {
		return fmt.Errorf("%w: %s", ErrInvalidOrderStatus, status)
	}

	if status <= order.Status || status != order.Status+1 {
		return fmt.Errorf("%w: can't transition from %s to %s", ErrInvalidOrderStatus, order.Status, status)
	}

	order.Status = status
	return nil
}

type OrderStatus int

const (
	OrderStatusInvalid OrderStatus = iota
	OrderStatusPending
	OrderStatusPaid
	OrderStatusShipped
	OrderStatusComplete
)

func (status OrderStatus) Valid() bool {
	return status > OrderStatusInvalid && status <= OrderStatusComplete
}

func (status OrderStatus) String() string {
	return "" // imagine some code here
}

Building

To build vischeck, simply run make build. This will produce a standalone binary (build/vischeck) and a plugin which can be used with golangci-lint. (build/vischeck.so - an example golangci-lint configuration is here).

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Analyzer = &analysis.Analyzer{
	Name: "vischeck",
	Doc:  "TODO",
	Run:  run,
}

Functions

This section is empty.

Types

This section is empty.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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