Library / SDK
protofire/solhint avatar
protofire/solhint

solhint's cache warning contradicts the sentence directly above it

Solhint is an open-source project to provide a linting utility for Solidity code.

1,127 stars198 forksJavaScriptMIT

At a glance

What is it?
A Solidity linter on npm with per-file configuration resolution, gitignore-style exclusions, plugins that resolve from outside the project folder, and an autofix list of nine rules. The documentation is where the interest is: the cache section says the configuration is hashed and then warns that it is not, the configuration hierarchy example names a file the same section says must not exist, and the usage text stops partway through an option.
Who is it for?
solhint suits a Solidity project that wants security and style rules enforced in one command, with a generated recommended ruleset, per-file overrides that merge rather than replace, and a real autofix for the mechanical rules. It is a poor fit if you need every rule to be fixable or every plugin to be guaranteed loaded, because autofix covers nine named rules and a plugin that fails to load is a warning rather than an error.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 3, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The cache section hashes the configuration and then says it does not

Read the cache documentation as two consecutive claims. The first describes the design: a flag enables caching, the tool stores a hash of each file's content and its effective configuration, and analysis is skipped when neither has changed. The second is a warning box directly underneath, and it says that if a file was analysed without error under a given configuration, that hash is stored, and that if the file is unchanged but the configuration file has new rules, the file will not be analysed. Those two cannot both be true. If the effective configuration were part of the hash, adding a rule would change it and force a re-run. The documented remedy is to remove the cache option. Two smaller details sit in the same section. There are two default locations described, one in the working directory and one under the dependency cache directory, with different file names. And the custom location flag is written as if the word option were part of the flag name.

The configuration example names a file the section says must not exist

The rule for hierarchical configuration is stated as a requirement: multiple configuration files can be used at once, they must all be named `.solhint.json`, and if that is not done the hierarchy will not work. The worked example immediately below it draws a tree whose interfaces folder contains a file called `solhint.json`, without the leading dot. So the illustration demonstrates the one case the text says is inert. The merge semantics are the genuinely useful part and are spelled out: the configuration closest to the analysed file wins for any rule present in both, while rules present in only one of the two remain active, so a nested file overrides rather than replaces. The same page notes that the explicit config flag is not compatible with multiple configurations, which means the hierarchy only works in discovery mode. A `--init` flag writes a starter file containing nothing but the recommended ruleset, and the older default ruleset is deprecated as of version 5.1.0.

Comments in the source can switch the linter off, line by line

The configuration surface extends inside the file being checked, which is the part worth arguing about on a team. A comment directive disables all validation for the line that follows it:

solidity
  // solhint-disable-next-line
  uint[] a;

The documentation also describes disabling specific rules on a given line rather than everything. For a linter whose stated purpose includes security validation, an in-source escape hatch that a contributor can add in the same commit as the code it silences is a review burden rather than a convenience, and it is worth a pull request template question. The same pattern of graceful degradation shows up in plugin loading, where a plugin that fails to load produces a warning and linting continues with the core rules and whatever other plugins did load. That is the right behaviour for an editor integration and the wrong behaviour for a release gate, because the difference between no plugin and a passing plugin becomes a line in a log rather than a non-zero exit.

Nine rules are auto-fixable out of the whole set

The fix option is documented as working on a named list, and the list is short: a throw-avoidance rule, a hashing-avoidance rule, the console rule, an explicit-types rule, a private-variable naming rule, a payable fallback rule, the quoting rule, a contract naming rule and a self-destruct-avoidance rule. Nine in total, against a full rule list that the page defers to a separate document. The overlap with the sample configuration is worth noticing, because two of those nine appear in the example config the page provides, one set to error and one to warn. That is the intended division of labour: the rules a machine can rewrite safely are the ones where the correct answer is mechanical, and naming style is in the list while anything requiring judgement is not. The repository backs this with a dedicated end-to-end file for autofix, alongside separate files for the core behaviour and for the formatters, which is a reasonable split for a tool whose formatter choice is part of its contract.

The usage text stops mid-option and the version check has no listed flag

Two things in the help output deserve attention. The option list in the documentation ends on an incomplete entry, a bare double dash followed by two letters, so the last flag in the table is truncated and the reader is left with an option that cannot be typed. Separately, the version-check paragraph says the tool checks whether newer versions exist and that a flag written as `--disc` avoids that check. That flag appears in prose only; it is not among the options the same section lists, and the name does not correspond to any word in the sentence that describes it. The check itself is real rather than incidental, because the dependency list includes a package whose purpose is fetching the latest version of a project. So the default behaviour of every lint run includes a network call to a version registry, and the documented way to turn it off is a flag you cannot read correctly. Other options in the list behave more predictably, including a warning threshold that also applies in quiet mode and a quiet flag whose help text carries a literal default annotation.

Publishing regenerates the rulesets and the rule documentation

The release pipeline is visible in the manifest, and it explains why generated files sit in the repository. The prepublish hook runs four steps in order: lint, test, then a ruleset generation step, then a documentation generation step. The ruleset step invokes a script and then runs the formatter over the output directory, so the shipped rulesets are generated and formatted rather than hand-maintained. The documentation step invokes a second script, which is why the full rule list lives in a document that a build produces. The publish allowlist is short and instructive: three entries, the configuration directory, the library directory and the executable shim. That is how the generated rulesets reach users while the test suites, the end-to-end suites, the scripts and the documentation stay out of the tarball. The current version in the manifest matches the newest release tag, and the executable is a single shim file at the repository root that resolves into the library.

The end-to-end suite installs the package globally, and its cleanup is not conditional

The local end-to-end script is a single shell string, and three details in it are worth reading. It deletes matching tarballs from the working directory first, then packs the project, then installs the resulting tarball globally, then runs the end-to-end command, and finally uninstalls the global copy. The cleanup step is joined with a semicolon rather than a conditional operator, so it runs whether or not the tests passed, which is the right instinct for not leaving a global install behind. The global install itself is the part to be careful about: the suite mutates machine-wide state to test the real artifact, and the failure mode of an interrupted run is a globally installed linter that shadows your development version. The three end-to-end files it runs are the core behaviour, the formatters and the autofix, with a timeout raised to ten seconds, and the coverage path wraps the same recursive suite in a coverage tool and then prints the report as a separate step.

The default branch is develop while the README links to master

A small inconsistency sits at the top of the page. The repository's default branch is `develop`, and the licence badge in the README points at a raw file URL on `master`. So the one link a reader is most likely to follow to check terms may not resolve against the branch they are reading. The rest of the project metadata is conventional: MIT, an original author and one named contributor with a company address, a funding file for the package registry, a coverage service configuration and a separate developer readme alongside the public one. Two repository-level files describe how the tool slots into other people's workflows rather than its own: a manifest of pre-commit hooks, which makes the linter consumable as a git hook, and a prettier configuration, which fixes the formatting the linter is not responsible for. Two icon files sit at the root, a container directory and an ignore file for it, and the release line is on the 6.2 series with the newest tag about seven weeks behind the last push to the branch.

Editorial conclusion

solhint suits a Solidity project that wants security and style rules enforced in one command, with a generated recommended ruleset, per-file overrides that merge rather than replace, and a real autofix for the mechanical rules. It is a poor fit if you need every rule to be fixable or every plugin to be guaranteed loaded, because autofix covers nine named rules and a plugin that fails to load is a warning rather than an error. Before you wire it into a repository, read two sections end to end: the cache section, whose stated behaviour and stated warning disagree and whose remedy is to delete the cache, and the configuration section, whose worked example contradicts its own rule about config file names.

Frequently asked questions

How do I set up solhint in a Solidity project?

Install it globally with npm, check it with `solhint --version`, then run `solhint --init`, which writes a `.solhint.json` file with the recommended ruleset enabled. After that you pass one or more globs, such as `solhint 'contracts/**/*.sol'`, or a single file path.

Which solhint rules can be fixed automatically?

Nine are listed: the throw and hashing avoidance rules, the console rule, explicit types, private variable naming, payable fallback, quotes, contract naming and the self-destruct avoidance rule. The full rule list is in a separate document in the repository.

Does the solhint cache pick up configuration changes?

The documentation is contradictory. One paragraph says the cache stores a hash of each file's content and its effective configuration, and the warning beneath says that new rules in the configuration file will not re-analyse an unchanged file. The stated remedy is to remove the cache option.

Can solhint load plugins from outside the project folder?

Yes, through configured plugin paths, resolved after the current working directory and then each path's own node_modules, which the page says exists for IDE and editor integrations. If a plugin fails to load, the tool warns and keeps linting with core rules and any other plugins that did load.

How do I exclude files from solhint?

With a `.solhintignore` file by default, or a custom filename via the ignore-path option. It uses gitignore syntax including negation with an exclamation mark, and the documented caveat is that unignoring a file also requires unignoring its parent folders.

Official sources

  1. License: MIT
  2. Project website
  3. protofire/solhint on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/protofire-solhint.svg)](https://hysenlabs.com/projects/protofire-solhint)