mvvmlint

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Aug 4, 2026 License: BSD-3-Clause Imports: 7 Imported by: 0

README

mvvmlint

ci Go Reference Go Report Card

A go/analysis analyzer that makes MVVM usage mandatory for applications built on the go-widgets toolkit.

The toolkit exposes mutable value fields on its widgets — a text entry's Text, a list's Items, a table's Rows, a tree's Root. Mutating those fields ad hoc scatters view state across imperative call sites and defeats the point of a ViewModel layer. The sanctioned way to touch widget state is through the go-widgets/mvvm binders, which take a pointer into the field:

mvvm.BindField(obs, &entry.Text, &entry.OnChange, invalidate) // sanctioned
entry.Text = "typed ad hoc"                                   // flagged

Run it in each app's CI as a required status check so non-MVVM code fails to merge.

Rules

The analyzer only fires in packages that import github.com/go-widgets/toolkit.

  1. Direct widget-state mutation. An assignment whose left-hand side is a selector X.Field, where the static type of X is (a pointer to) a toolkit widget type and Field is in the state-field allowlist. This is the core "mutate ad hoc" violation.
    • The address-of form &w.Field (what the binders take) is not an assignment to a selector and is never flagged.
    • A composite literal toolkit.Entry{Text: "x"} seeds a field at construction and is never flagged.
    • Callback slots (OnChange, OnClick, …) are not state fields, so composing a handler is never flagged.
  2. Missing view-model. A package that imports the toolkit but not go-widgets/mvvm earns one diagnostic, anchored on the toolkit import: "widgets present but no MVVM view-model; wire state through github.com/go-widgets/mvvm". Turn it off with -requirevm=false for pure widget libraries that legitimately ship no ViewModel.
Default state-field allowlist

Derived from the mutable state fields of the toolkit widgets:

Active  Checked  Columns  Content  Current  Cursor  Data  Expanded  Fraction
High    Items    Low      Max      Min      Nodes   On    Options   Page
Progress Range   Root     Rows     ScrollRow Selected Text Value    Values

Override it with -statefields=Text,Items,Selected,... (comma-separated). When set, the flag replaces the default set.

Exemptions (opt out of rule 1)
  • Files named *_binding.go — the conventional home for hand-written binding glue — are exempt.
  • Any file containing a //mvvmlint:allow directive is exempt.

Install & run locally

go install github.com/go-widgets/mvvmlint/cmd/mvvmlint@latest
go vet -vettool="$(go env GOPATH)/bin/mvvmlint" ./...

Flags are passed through go vet (unprefixed, since this is a single-analyzer vet tool):

go vet -vettool="$(which mvvmlint)" -requirevm=false ./...
go vet -vettool="$(which mvvmlint)" -statefields=Text,Items ./...

CI gate

Reference the shipped reusable workflow from your app repo and mark the resulting check as required in branch protection:

# .github/workflows/mvvm.yml in your app repo
name: mvvm
on:
  pull_request:
  push:
    branches: [main]
jobs:
  mvvm:
    uses: go-widgets/mvvmlint/.github/workflows/mvvmlint.yml@main
    # optional overrides:
    # with:
    #   go-version: "1.26.4"
    #   packages: "./..."
    #   version: "latest"
Standalone job (copy-paste)

For repos that prefer inlining the steps:

jobs:
  mvvm:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: "1.26.4"
      - run: go install github.com/go-widgets/mvvmlint/cmd/mvvmlint@latest
      - run: go vet -vettool="$(go env GOPATH)/bin/mvvmlint" ./...

False-positive posture

The rule is deliberately conservative — it fires only when both the static receiver type resolves to a toolkit widget and the field is on the allowlist, so a same-named field on an unrelated type (myConfig.Text = ...) is never touched. The remaining intentional edges — low-level binding glue, or a widget subsystem that must poke fields directly — are handled by the *_binding.go naming convention and the //mvvmlint:allow directive, and rule 2 can be disabled wholesale for pure widget libraries with -requirevm=false.

License

BSD-3-Clause — see LICENSE. Copyright (c) 2026 the go-widgets/mvvmlint authors.

Documentation

Overview

Package mvvmlint provides a go/analysis analyzer that makes MVVM usage mandatory for applications built on the go-widgets pixel/cell toolkit.

The toolkit exposes mutable value fields on its widgets (a text entry's Text, a list's Items, a table's Rows, and so on). Mutating those fields ad hoc scatters view state across imperative call sites and defeats the point of a ViewModel layer. The sanctioned way to touch widget state is through the go-widgets/mvvm binders, which take a POINTER into the field (BindField(obs, &w.Text, &w.OnChange, invalidate)); the address-of form is a binding, not a mutation, and is never flagged.

The analyzer reports two rules, but only in packages that import the toolkit:

  1. Direct widget-state mutation. An assignment whose left-hand side is a selector X.Field, where the static type of X is (a pointer to) a toolkit widget type and Field is in the configurable state-field allowlist. This is the core "mutate ad hoc" violation. Composite literals and the address-of form &w.Field are not assignments to a selector and are never flagged. Assignments in files named *_binding.go, and any file carrying a //mvvmlint:allow directive, are exempt so intentional glue can opt out.

  2. Missing view-model. A package that imports the toolkit but does not import go-widgets/mvvm gets a single diagnostic asking that state be wired through a view-model. This rule can be turned off (via -requirevm=false) for pure widget libraries that legitimately ship no ViewModel.

Index

Constants

View Source
const MVVMPath = "github.com/go-widgets/mvvm"

MVVMPath is the import path of the go-widgets MVVM library.

View Source
const ToolkitPath = "github.com/go-widgets/toolkit"

ToolkitPath is the import path of the go-widgets pixel toolkit.

Variables

View Source
var Analyzer = newAnalyzer()

Analyzer is the mvvmlint go/analysis analyzer. Use it with singlechecker, or as a go vet tool: go vet -vettool=$(which mvvmlint) ./...

View Source
var DefaultStateFields = []string{
	"Active",
	"Checked",
	"Columns",
	"Content",
	"Current",
	"Cursor",
	"Data",
	"Expanded",
	"Fraction",
	"High",
	"Items",
	"Low",
	"Max",
	"Min",
	"Nodes",
	"On",
	"Options",
	"Page",
	"Progress",
	"Range",
	"Root",
	"Rows",
	"ScrollRow",
	"Selected",
	"Text",
	"Value",
	"Values",
}

DefaultStateFields is the built-in allowlist of toolkit widget value fields that carry display or data state. Assigning to one of these on a toolkit widget is the "mutate ad hoc" violation the analyzer exists to catch.

The set is intentionally curated to value/state fields (not callback slots such as OnChange, and not one-time construction options): it is derived from the mutable state fields of the toolkit widgets — an entry's Text, a list's Items and Selected, a table's Rows/Columns, a tree's Root, a slider's Value/Low/High, a check's Checked, a switch's On, and so on.

Functions

This section is empty.

Types

This section is empty.

Directories

Path Synopsis
cmd
mvvmlint command
Command mvvmlint is a standalone driver for the mvvmlint analyzer.
Command mvvmlint is a standalone driver for the mvvmlint analyzer.

Jump to

Keyboard shortcuts

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