Papyrus Lint goes far beyond style

PapyrusCompiler.exe only checks that a script is syntactically valid — it will happily compile a script that dereferences a None object at runtime, passes a String where an Int is expected, returns the wrong type from a function, compares an Int to a Float inexactly, or contains a branch that can never execute. Papyrus Lint performs deep correctness, reliability, and performance checks across your .psc source, on top of the formatting and style checks a linter usually provides — as a desktop app, a CLI, a LSP Server and editor extensions.

A simple example

This compiles cleanly, but silently passes the wrong argument:

Compiles, but wrong
Function DoSomething(Actor a, Actor b, Form SomeItem)
    a.RemoveItem(SomeItem, 1, b);
EndFunction
What was likely meant
Function DoSomething(Actor a, Actor b, Form SomeItem)
    a.RemoveItem(SomeItem, 1, false, b);
EndFunction
Or, if you meant it, silence it
Function DoSomething(Actor a, Actor b, Form SomeItem)
    a.RemoveItem(SomeItem, 1, b); ; @disable
EndFunction

The Strict boolean check lint catches this: the first call passes the Actor value b to a Bool parameter — a mistake that cost real debugging time in one of the author's own mods. A trailing ; @disable comment silences it on that line, for the rare case where you actually meant to write it that way.

What is a linter?

A linter is a tool that scans source code for patterns that are likely to be mistakes, bad practice, or inconsistent style, without actually running the code. It works from heuristics — recognizable patterns known to often cause problems — rather than proving that a given line is definitely wrong. Because of that, a linter can produce false positives. Treat every diagnostic as advice, not a guaranteed defect report: use your own judgment, and use an ; @disable comment to silence a rule you've decided doesn't apply.

What this is NOT

A compiler (it uses the standard Papyrus compiler under the hood), an editor, a guarantee a script is fit for purpose, or a replacement for proper testing.

What this IS

A desktop app, standalone CLI, and editor extensions that all share one lint/repair engine, so a diagnostic looks and behaves the same everywhere you run it. It supports Skyrim Special Edition / Anniversary Edition, Skyrim Legendary Edition, Fallout 4, and Starfield.

See it in action

Drop a .achlist, a single .psc file, a .ppj file, or a folder to scan recursively, to get started.

Papyrus Lint's drag-and-drop import screen
Drop your file or achlist to get started
Papyrus Lint's lint results list
Lint results, filterable by severity, tag, and importance
Papyrus Lint's code viewer with syntax highlighting
Code viewer with syntax highlighting and inline fixes
Papyrus Lint's mass fix view for automatically repairing many findings at once
Mass fix: automatically repair every fixable finding at once
Papyrus Lint's VS Code extension showing inline diagnostics
The VS Code extension
Papyrus Lint's command-line interface output
The command-line interface

Implemented lints

Papyrus Lint performs deep correctness, reliability, and performance checks across your .psc source, on top of the formatting and style checks a linter usually provides, so a mod author finds these problems at write time instead of from a bug report. Every rule works from raw source or lexer tokens rather than requiring a script that parses cleanly. For a searchable, filterable reference of every implemented lint, with each rule's id, severity, tags, auto-fix support, and full documented behavior, see the full lint rule reference.

Add a trailing ; @disable <rule-id>[, <rule-id>...] comment to a line to suppress diagnostics from those rules on that line only (e.g. action = 1 ; @disable float-to-int); a bare ; @disable suppresses every rule on the line. A ; @disable-file <rule-id>[, <rule-id>...] comment does the same across the whole file instead, no matter which line it's written on (e.g. ; @disable-file float-to-int). See the full lint rule reference for each rule's id.

Command-line interface

The standalone PapyrusLinterCLI binary lints an .achlist, a .psc, or a directory without a GUI. See the CLI reference for subcommands, project resolution, and report formats.

PapyrusLinterCLI help
PapyrusLinterCLI version
PapyrusLinterCLI lint path/to/project.achlist
PapyrusLinterCLI lint path/to/Project.ppj
PapyrusLinterCLI init --game skyrim
PapyrusLinterCLI init --game skyrim --preset standard
PapyrusLinterCLI init --game fallout4 --preset my-team
PapyrusLinterCLI preset add my-team path/to/papyrus-lint.yaml
PapyrusLinterCLI preset add my-team path/to/papyrus-lint.yaml --yes
PapyrusLinterCLI preset list
PapyrusLinterCLI doctor path/to/project.achlist
PapyrusLinterCLI doctor --format json path/to/project.achlist
PapyrusLinterCLI doctor path/to/Project.ppj
PapyrusLinterCLI lint path/to/Example.psc
PapyrusLinterCLI lint path/to/scripts/source
PapyrusLinterCLI fix path/to/project.achlist
PapyrusLinterCLI fix path/to/Example.psc
PapyrusLinterCLI fix --type trailing-whitespace path/to/Example.psc
PapyrusLinterCLI fix --line 12 --type trailing-whitespace path/to/Example.psc
PapyrusLinterCLI fix --dry-run path/to/project.achlist
PapyrusLinterCLI lint --tag style path/to/project.achlist
PapyrusLinterCLI fix --tag style path/to/project.achlist
PapyrusLinterCLI lint --format json path/to/project.achlist
PapyrusLinterCLI lint --format ai path/to/project.achlist
PapyrusLinterCLI lint --format ai --hash-source path/to/project.achlist
PapyrusLinterCLI fix --format json path/to/project.achlist
PapyrusLinterCLI lint --config path/to/papyrus-lint.yaml path/to/Example.psc
PapyrusLinterCLI lint --script-root path/to/SharedScripts path/to/project.achlist
PapyrusLinterCLI lint --output path/to/report.txt path/to/project.achlist
PapyrusLinterCLI lint --format json --output path/to/report.json path/to/project.achlist
PapyrusLinterCLI lint --short-paths path/to/project.achlist
PapyrusLinterCLI lint --color never path/to/project.achlist
PapyrusLinterCLI lint --progress --output path/to/report.txt path/to/project.achlist
PapyrusLinterCLI lint --threads 8 path/to/project.achlist
PapyrusLinterCLI lint --threads 1 path/to/project.achlist
PapyrusLinterCLI lint --blob "ScriptName Example extends ObjectReference"
PapyrusLinterCLI lint --format json --blob "ScriptName Example extends ObjectReference"
PapyrusLinterCLI lint --config path/to/papyrus-lint.yaml --blob "ScriptName Example extends ObjectReference"

It exits 0 when nothing failing was found, 1 when something was, and 2 on a usage or I/O error — so it can gate a CI step on a clean lint run. The preferred way to run it in CI is the Papyrus Lint GitHub Action (idrinth/papyrus-lint-action), which posts findings as inline PR review comments. See the CLI reference for every flag.

Configuration

Lint/fix behavior is configured via an optional papyrus-lint.yaml (or .yml) at the project root. Keys you omit fall back to their documented defaults. PapyrusLinterCLI init writes the full default file. See the configuration reference for where that file is resolved and what every key does.

Integrations

The desktop app, CLI, LSP Server and both editor extensions all use the same lint and repair engine, so a diagnostic matches wherever you run it.

VS Code extension

Lints and fixes .psc files by invoking PapyrusLinterCLI --json.

View source

SublimeLinter plugin

Runs PapyrusLinterCLI against a saved .psc and reports SublimeLinter diagnostics.

View source

LSP Server

Runs Papyrus Lint as a LSP server to provide linting and fixing for other editors.

View source

GitHub Action

Runs Papyrus Lint in CI and posts findings as inline review comments on pull requests.

Documentation

Reference material from the project's docs/ directory, published here as browsable pages alongside their raw source on GitHub.

Thank Yous

A big thank you to WraithFallen for doing a massive testing run on the versions of this tool, helping find bugs and improve it further with their dedication to rooting out false positives.

Scrivener07 deserves a huge thank you for helping me review the linting rules in a long call. I appreciate the feedback and willingness to test what I assumed to be best practice.

Another thank you to s3ngine and wall416 over on NexusMods for spotting bugs and reporting them in the early development of the tool.

Also thank you to DavidJCobb from the r/skyrimmods discord for giving me critical feedback and encouraging me further to clean up the ai slop here.

A last thank you to Vict over at x.com/VictMangle for the new logo.

How to help

You can help the project by: