Open-source project
jslint-org/jslint avatar
jslint-org/jslint

JSLint: a zero-dependency JavaScript linter and V8 coverage tool you download as one file

JSLint, The JavaScript Code Quality and Coverage Tool

3,655 stars473 forksJavaScriptUnlicense

At a glance

What is it?
JSLint ships as a single jslint.mjs file with no npm dependencies, and it also turns Node's V8 coverage output into a report. Here is how to install it, how the directives work, and where it stops being the right tool.
Who is it for?
Adopt JSLint if you want a linter with no dependency tree that you can vendor as one file, or if you need V8 coverage merged into a report from the same script. Do not adopt it expecting ESLint's plugin ecosystem or a configurable rule set; JSLint's directives are a fixed vocabulary and the README does not document a plugin API.
Can I use it commercially?
Yes. Unlicense 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 5 days 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 September 27, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What JSLint solves, and who it is written for

Most JavaScript linters arrive as a package with a dependency tree, a config file format, and a plugin registry. JSLint takes the opposite position. The distribution is a single file, jslint.mjs, that you download and run. The package.json for @jslint-org/jslint lists no dependencies at all, and the keywords in that file include zero-config and zero-dependency. That is the whole pitch: nothing to install, nothing to keep updated on a schedule, nothing that can break because a transitive package changed.

The second half of the tool is coverage. The description calls JSLint "The JavaScript Code Quality and Coverage Tool", and the README carries a Quickstart section for creating a V8 coverage report from a Node.js or npm program. So the intended user is someone running Node, who wants both a style and correctness check and a coverage number, and who would rather not assemble two separate toolchains to get them.

This is not a linter for a team that wants to debate rule configuration. It is for a maintainer, a small project, or a CI job where the tool itself should be the least interesting part of the pipeline.

How JSLint is structured: one module, three ways in

The repository is deliberately flat. The top-level entries include jslint.mjs, jslint_wrapper_cjs.cjs, jslint_wrapper_codemirror.js, jslint_wrapper_vim.vim, jslint_wrapper_vscode.js, index.html, test.mjs and jslint_ci.sh. There is no src directory and no build step implied by the layout.

The package.json exports map defines two entry points. The import condition resolves to ./jslint.mjs, so an ES module import gets the real implementation. The default condition resolves to ./jslint_wrapper_cjs.cjs, which is how CommonJS callers reach the same code. The bin field maps the command jslint to jslint.mjs, so an npm install of the package gives you a jslint executable.

Behaviour is controlled by directives inside the source being linted rather than by an external config file. The README documents /*jslint*/, /*global*/, /*property*/, a disable and enable pair written as /*jslint-disable*/ ... /*jslint-enable*/, a single-line //jslint-ignore-line, and the matching coverage directives /*coverage-disable*/ ... /*coverage-enable*/ and //coverage-ignore-line. That design keeps the exceptions next to the code they excuse, which is convenient for review and awkward for anyone who wants one place to see every exemption in a project.

Installing JSLint and running it on a first file

The README does not route you through npm for the quickstart. The install step is a download: fetch https://www.jslint.com/jslint.mjs and save it as jslint.mjs in your working directory.

bash
curl -L https://www.jslint.com/jslint.mjs > jslint.mjs

After that command you should have a jslint.mjs file next to whatever you are working on. Nothing else is fetched, which is the point of the single-file distribution.

To try it, write a trivial file and point the script at it. The README uses exactly this example.

bash
printf "console.log('hello world');\n" > hello.js
node jslint.mjs hello.js

Running that should produce JSLint's warnings for hello.js on standard output. If the file is clean by JSLint's standards, there is nothing to read.

If you would rather call it from code, the README shows an ES module import that passes options and a globals list. The call signature is jslint(source, options, globals), and the result carries a warnings array whose entries have a formatted_message field.

js
import jslint from "./jslint.mjs";

let globals = ["caches", "indexedDb"];
let options = {browser: true};
let source = "console.log('hello world');\n";

jslint.jslint(source, options, globals).warnings.forEach(function ({
    formatted_message
}) {
    console.error(formatted_message);
});

That block is the shape to copy when you want to lint a string you already hold in memory rather than a path on disk. The README also lists a section for linting an entire directory from the shell, and separate quickstarts for autofixing whitespace, for producing a JSLint report, and for V8 coverage reports.

The coverage side: V8 output, not instrumentation

JSLint does not instrument your source to measure coverage. It consumes V8 coverage, which is the data Node already produces when you ask it to. The README's section is titled "To create V8 coverage report from Node.js / Npm program", and the package's own test script shows the wiring: node jslint.mjs v8_coverage_report=.artifact/coverage node test.mjs.

Read that script carefully, because it is the clearest statement of the design in the repository. The v8_coverage_report= argument is passed to jslint.mjs, and then the command that follows is the program under test. JSLint is not a wrapper library your test suite imports. It sits in front of the Node invocation, collects what V8 emits, and writes the report to the directory you named.

The practical consequence is that coverage works for anything Node runs, including a plain script with no test framework, as long as you can put jslint.mjs in front of it. It also means coverage is tied to V8. A runtime that does not emit V8 coverage data is outside the scope of this feature.

The repository also carries test_coverage_merge_data.json and a jslint_ci.sh script, and the Status table in the README links per-branch coverage badges hosted under jslint-org.github.io. The project uses its own coverage path on itself, which is the strongest evidence available that the path is exercised.

Where JSLint is the wrong tool

The first limitation is the one that generates most of the friction: JSLint is opinionated and the opinions are not yours. There is no rule registry to enable or disable individually. The documented control surface is the directive list, and the README does not describe a plugin API or a way to add your own checks. If your team's style guide differs from JSLint's, you adapt to JSLint or you pick a different linter. The /*jslint-disable*/ and //jslint-ignore-line directives let you carve out exceptions, but they do not let you redefine the rule.

The second is ECMAScript coverage. The README has a section on ECMAScript Feature Support, and the release notes for v2026.7.30 mention adding an ES2015 feature for..of. That phrasing tells you the language surface is added incrementally over time rather than tracking the specification automatically. Before adopting, check that the syntax your codebase actually uses is in the supported set; a recent addition landing in a release is a signal that the set is not complete.

The third is the absence of an ecosystem. There is no marketplace of shareable configs, no framework-specific presets, and no editor integration beyond the wrappers in the repository itself: jslint_wrapper_codemirror.js, jslint_wrapper_vim.vim and jslint_wrapper_vscode.js. Those exist, and the README has quickstarts for CodeMirror, Vim and VSCode, but they are three hand-written integrations rather than a plugin surface third parties build on.

Finally, the README does not document a rollback or pinning procedure for the downloaded file. If you vendor jslint.mjs by curl, your build is only as reproducible as your own copy of that file. Recording a hash alongside it is your responsibility, not something the install instructions cover. The release notes for v2026.8.31 mention updating a CI shell function to include a sha256 hash of files, so hashing is on the project's mind for its own pipeline, but the README's install section is still a bare curl.

JSLint against ESLint and JSHint

The honest comparison is about who owns the rule set. ESLint exposes configuration and a plugin system; you assemble the policy, and the ecosystem supplies presets for React, TypeScript and most frameworks. That flexibility is the reason it dominates, and it is also the reason an ESLint setup can grow a config file and a dependency list that need their own maintenance.

JSLint inverts that. The policy is fixed by the tool, and the directives in the source are the only negotiation. You give up control and you get a linter with no dependency tree that you can drop into a repository as one file. For a small project, a teaching codebase, or a CI step where you do not want to audit a lockfile, that trade is reasonable.

JSHint sits between the two. It kept the older linter's shape but made rules configurable, which is why it appears in the same searches as JSLint. If your complaint about JSLint is specifically that you cannot turn a rule off, JSHint is the closer answer. If your complaint is that a linter should not have a dependency tree at all, JSLint is the one that ships as a single file.

One more distinction that the search results blur: JSLint validates JavaScript, and the repository is a JavaScript project with a jslint.mjs entry point. It is not a JSON validator, despite the overlapping name with JSON tooling. The directives and the coverage features are both about JavaScript source.

Maintenance, licence and what upgrading costs

The repository is not archived, and the last push was on 2026-09-23. Releases are dated and frequent: v2026.8.31 on 2026-09-04, v2026.7.30 on 2026-08-02, v2026.6.30 on 2026-07-05. The version string in package.json is 2026.9.29, and the README's Status table labels the master branch with that same version. So the project is being cut on a monthly-ish cadence, and the changelog entries are specific: a CI shell function gaining a sha256 hash, an ES2015 for..of feature, a README update documenting supported ES2015+ features.

That cadence has a cost. Because JSLint's rule set is the product, a new release can surface warnings on code that was clean before. Upgrading is not a no-op the way bumping a formatting-only tool can be. If you vendor jslint.mjs, treat the file as a pinned artifact and read the changelog before replacing it, particularly the jslint-ecma entries that add language features.

The licence is the Unlicense, recorded as UNLICENSE in package.json and as LICENSE at the repository root. The Unlicense is a public-domain dedication rather than a permissive licence with conditions, which in practice means there is no attribution requirement to satisfy and no copyleft obligation to reason about. That is a description of the licence text, not legal advice; if the distinction matters to your organisation, read the LICENSE file and take your own counsel.

There is also a devops section in the README covering pull-request merge, branch-master commit, branch-master publish and vscode-jslint publish. That is documentation of the maintainer's own release process, not a guide for consumers, but it does tell you the branches are master, beta and alpha, with the web demo pointed at beta and package.json exports resolving to the module files rather than to a branch.

Editorial conclusion

Adopt JSLint if you want a linter with no dependency tree that you can vendor as one file, or if you need V8 coverage merged into a report from the same script. Do not adopt it expecting ESLint's plugin ecosystem or a configurable rule set; JSLint's directives are a fixed vocabulary and the README does not document a plugin API. Before committing, run node jslint.mjs on your existing codebase and read the warning volume, then check that your Node version can load jslint.mjs as an ES module.

Frequently asked questions

How do I use JSLint?

The README's quickstart is a download rather than an npm install: fetch https://www.jslint.com/jslint.mjs and save it as jslint.mjs. Then run node jslint.mjs hello.js against a file, or import jslint.mjs as an ES module and call jslint(source, options, globals).

Who created JSLint?

The README credits Douglas Crockford, listing his name and the address [email protected] directly under the project title.

What is the difference between JSHint and JSLint?

The README describes JSLint's control surface as a fixed set of directives rather than a configurable rule set, and it documents no plugin API. JSHint itself is not covered by this repository, so the difference beyond that cannot be stated from these facts.

What is ESLint used for?

This repository does not document ESLint, so its purpose cannot be stated from these facts. What the README does cover is JSLint's own model: a fixed rule set, directives such as /*jslint*/ and //jslint-ignore-line, and no plugin API.

Official sources

  1. jslint-org/jslint on GitHub
  2. License: Unlicense
  3. Project website
  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/jslint-org-jslint.svg)](https://hysenlabs.com/projects/jslint-org-jslint)