Skip to content
z-shellPublic

About

A linter for Zsh scripting language.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

37 stars

Watchers

1 watching

Forks

Zsh Lint logo

Zsh Lint

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

Go CI status Latest release Go version License: GPL-3.0 Enforces Zsh Plugin Standard v2 rules

zsh-lint is a standalone, Go-based semantic analyzer for Zsh. It parses Zsh source into an abstract syntax tree with the Zsh dialect (LangZsh) of mvdan/sh, evaluates static analysis rules, enforces Zsh Plugin Standard v2, and reports compiler-style diagnostics or structured JSON. The parser is a maintained fork in third_party/mvdan-sh that adds Zsh grammar upstream does not yet cover; it does not parse Zsh as POSIX sh or Bash. General shell linters such as ShellCheck reject Zsh scripts, so zsh-lint covers Zsh code and Zsh plugins directly.

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.27 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:3:1: [plugin/unload-function] Unload function 'example_plugin_unload' should unfunction itself upon completion

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

Beyond the unit tests, CI runs the parser fork's upstream and Zsh grammar tests with native-shell confirmation, checks the repository's Zsh files with zsh -n, and parses and lints a pinned reference corpus of real Z-Shell projects. Parser changes are also verified with sampled mutation testing, as the parser-gap workflow describes.

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.

Documentation and ecosystem links

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.

About

A linter for Zsh scripting language.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

37 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages