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

zsh-lint

module
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2026 License: GPL-3.0

README

Zsh Lint logo

Zsh Lint

Standalone Go-based semantic analyzer and static linter for Zsh scripts.

Go CI status Latest release Go version Enforces Zsh Plugin Standard v2 rules

zsh-lint is a standalone, Go-based semantic analyzer for Zsh. It parses shell source code into an abstract syntax tree using mvdan/sh, evaluates static analysis rules, enforces Zsh Plugin Standard v2, and reports compiler-style diagnostics or structured JSON.

[!IMPORTANT] Canonical documentation lives on the Z-Shell Wiki: wiki.zshell.dev/community/zsh_lint. Consult the wiki for end-user guides, file selection strategies, suppression syntax, CI workflows, and the published rule reference.

Features

  • Semantic static analysis: Evaluates syntax trees for unquoted variables, backquote command substitutions, special parameter shadowing, unsafe eval calls, and style issues without executing scripts.
  • Zsh Plugin Standard v2 enforcement: Every run checks unload functions, function-scoped options, $0 handling, and fpath hygiene. Projects with a validated zsh-lint.json additionally get the z-shell/project@2 profile: function and parameter namespaces, the shared Plugins registry, load-only helpers, and repeated external commands.
  • Automatic project discovery: Discovers zsh-lint.json configuration files up the directory hierarchy to contextualize standalone scripts, plugin entrypoints, autoloaded functions, and completions.
  • Inline suppression: Silences one intentional finding at a time with # zsh-lint disable=<rule-id> -- reason; suppressions must name a rule and are audited through meta/* diagnostics.
  • Greppable and JSON diagnostics: Outputs standard file:line:col: [rule] message diagnostics for terminal and editor workflows, or structured JSON for automated CI checks.
  • Corpus survey tooling: Includes the companion zsh-lint-survey CLI to survey parser front-end coverage across large Zsh codebases without running lint rules.

Requirements

  • Go 1.26 or newer (when compiling from source or installing via go install).

[!NOTE] zsh-lint neither sources nor executes the files it analyzes, and it does not call zsh. A native syntax check such as zsh -f -n -- file.zsh is a separate, recommended step.

Installation

Go install

Build and install the latest tagged release:

go install github.com/z-shell/zsh-lint/cmd/zsh-lint@latest

To install the parser survey tool:

go install github.com/z-shell/zsh-lint/cmd/zsh-lint-survey@latest

Ensure $(go env GOPATH)/bin is included in your PATH.

Build from source

git clone https://github.com/z-shell/zsh-lint.git
cd zsh-lint
go build ./cmd/zsh-lint

Zi plugin manager

Let Zi clone the repository, build the binary with the local Go toolchain, and put it on PATH:

zi as"program" nocompletions atclone"go build ./cmd/zsh-lint" atpull"%atclone" pick"zsh-lint" for z-shell/zsh-lint

Usage

Pass one or more file paths to zsh-lint:

zsh-lint script.zsh plugin.plugin.zsh

Command-line options

Option Type Description
--config PATH string Explicit project configuration path; disables discovery.
--no-config boolean Disable automatic zsh-lint.json discovery and use default rules.
--format=json string Emit machine-readable diagnostics in JSON format.

--config and --no-config are mutually exclusive.

Diagnostic output

Standard terminal output format:

script.zsh:2:8: [quoting/unquoted-var] Variable expansion should be double-quoted
plugin.plugin.zsh:1:1: [plugin/unload-function] Plugin registers persistent hooks or widgets but defines no '<name>_plugin_unload' function

Findings at error or warning severity fail the run; info and hint findings are reported but do not change the exit code. The rule reference lists every rule with its severity.

[!NOTE] The CLI analyzes each explicitly provided file path as Zsh source. It does not recurse into directories or filter files by shebang.

Project configuration

Add a zsh-lint.json file at the root of a repository to provide project context for multi-file plugins and tools. zsh-lint walks upward from each input file to find the nearest configuration automatically.

{
  "version": 2,
  "project": {
    "kind": "plugin",
    "minimum_zsh": "5.8",
    "identifier": "example"
  },
  "sources": [
    { "root": "example.plugin.zsh", "profile": "sourced-library" },
    { "root": "functions", "profile": "autoload-function" },
    {
      "root": "completions",
      "profile": "autoload-function",
      "role": "completion"
    },
    { "root": "tests", "profile": "test-fixture" }
  ]
}

A validated version 2 configuration selects the z-shell/project@2 rule profile automatically; there is no profile key to set. This file is examples/plugin/zsh-lint.json, and examples/standalone shows the application kind. Schema fields and source profiles are described in the Project Configuration Guide.

Advanced: Parser survey utility (zsh-lint-survey)

zsh-lint-survey executes the parser front end across target files and reports parser gaps without evaluating static analysis rules. It is used for corpus validation and upstream syntax compatibility tracking.

zsh-lint-survey path/to/*.zsh
zsh-lint-survey -trace-parses path/to/file.zsh   # also report parse count and adapter depth on stderr
zsh-lint-survey -compare ./base/zsh-lint-survey -native path/to/*.zsh   # verdict changes against a base build
zsh-lint-survey -compare ./base/zsh-lint-survey -native -runtime -table rows.md grid/*.zsh   # probe rows, run where zsh -n cannot decide
zsh-lint-probe -bodies bodies.txt -out grid/   # place construct variants in every scanner context
zsh-lint-probe -rows rows.txt -out grid/       # one probe file per line
zsh-lint-survey -judge path/to/file.zsh        # gap, false accept or agree, by zsh -f -n and zsh-lint
zsh-lint-survey -reduce path/to/file.zsh > reduced.zsh   # shrink a gap or false accept, keeping its message and place
bash .github/scripts/verify-parser-change.sh --rows rows.txt   # a parser change's whole verification
Advanced: JSON output contract

--format=json writes one versioned envelope to stdout. Parser failures share it under the reserved rule parse/error, and an unpositioned diagnostic omits range:

{
  "version": 1,
  "diagnostics": [
    {
      "rule": "quoting/unquoted-var",
      "severity": "info",
      "message": "Variable expansion should be double-quoted",
      "file": "script.zsh",
      "range": {
        "start": { "line": 2, "column": 8, "offset": 15 },
        "end": { "line": 2, "column": 10, "offset": 17 }
      }
    }
  ],
  "summary": {
    "files": 1,
    "diagnostics": 1,
    "errors": 0,
    "warnings": 0,
    "infos": 1,
    "hints": 0
  }
}

The full contract, including sort order and versioning rules, is docs/project/output-contract.md.

Exit code conventions
Exit code Description
0 Every input parsed, and no finding at error or warning severity; info and hint findings may still be printed.
1 At least one parser failure, unreadable input file, per-file configuration discovery failure, or finding at error or warning severity.
2 Invocation error: unknown or repeated flags, no input paths, --config with --no-config, or an explicit --config file that fails to load or does not cover an input path.

Verification

Run the test suite and static analysis from the repository root:

go build ./... && go vet ./... && go test ./...
golangci-lint run ./...

To regenerate API and code-derived reference documentation:

go tool gomarkdoc --output ref.md ./cmd/zsh-lint ./cmd/zsh-lint-survey ./internal/survey ./internal/rules

Contributor workflows and local testing practices are documented in docs/README.md. Report parser gaps and propose rules through the issue forms; anything else can be a blank issue. Pull requests link their owning issue or carry the meta:no-issue label.

Release model

Contributions integrate on main via squash merge after review. Annotated vX.Y.Z tags mark official releases and trigger automated publication workflows.

Contributing and license

Contributions follow the Z-Shell Organization Guidelines. Distributed under the terms of the GNU General Public License v3.0. See LICENSE for details.


Developed with ❤️ by the Z-Shell Community.

Directories

Path Synopsis
cmd
wikidoc command
Command wikidoc is a CI/dev tool (not product code) that reads a gomarkdoc-generated Markdown file, sanitizes it for MDX compatibility, and injects the result into a marked region of a Docusaurus .mdx page in place.
Command wikidoc is a CI/dev tool (not product code) that reads a gomarkdoc-generated Markdown file, sanitizes it for MDX compatibility, and injects the result into a marked region of a Docusaurus .mdx page in place.
zsh-lint command
Command zsh-lint performs static analysis of Zsh shell scripts.
Command zsh-lint performs static analysis of Zsh shell scripts.
zsh-lint-probe command
Command zsh-lint-probe writes a probe grid for a parser change (#428): every body from the body file placed in every scanner context, one file each.
Command zsh-lint-probe writes a probe grid for a parser change (#428): every body from the body file placed in every scanner context, one file each.
zsh-lint-survey command
Command zsh-lint-survey runs the parser front end across Zsh files and reports parser gaps without evaluating static-analysis rules.
Command zsh-lint-survey runs the parser front end across Zsh files and reports parser gaps without evaluating static-analysis rules.
internal
diag
Package diag defines the renderer-agnostic, parser-neutral diagnostics model shared by zsh-lint's analyzer: rule identity, severity, message, and source range.
Package diag defines the renderer-agnostic, parser-neutral diagnostics model shared by zsh-lint's analyzer: rule identity, severity, message, and source range.
parse
Package parse wraps the mvdan.cc/sh parser as the analyzer's front end.
Package parse wraps the mvdan.cc/sh parser as the analyzer's front end.
probe
Package probe generates probe grids for parser changes (#428): each body, a variant of the construct under repair, placed in each context a compatibility adapter's source scanner can meet.
Package probe generates probe grids for parser changes (#428): each body, a variant of the construct under repair, placed in each context a compatibility adapter's source scanner can meet.
projectconfig
Package projectconfig loads and resolves explicit zsh-lint project metadata.
Package projectconfig loads and resolves explicit zsh-lint project metadata.
reduce
Package reduce shrinks a source while a predicate holds, by delta debugging over lines and then over shell tokens (#562).
Package reduce shrinks a source while a predicate holds, by delta debugging over lines and then over shell tokens (#562).
suppress
Package suppress implements the inline suppression contract defined in docs/project/suppression.md (#19, implemented under #65).
Package suppress implements the inline suppression contract defined in docs/project/suppression.md (#19, implemented under #65).
survey
Package survey runs the parser front end across a set of Zsh files and reports, per file, whether parsing succeeded.
Package survey runs the parser front end across a set of Zsh files and reports, per file, whether parsing succeeded.
wikidoc
Package wikidoc transforms gomarkdoc-generated Markdown into MDX-safe content and injects it into a marked region of a Docusaurus .mdx page.
Package wikidoc transforms gomarkdoc-generated Markdown into MDX-safe content and injects it into a marked region of a Docusaurus .mdx page.

Jump to

Keyboard shortcuts

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