JavaScript Standard Style, migrated from JavaScript to Rust.
This is a port of standard 17.1.2. It
checks JavaScript source for Standard Style violations; only the checker
itself changed language.
npm install standard becomes:
cargo install standard # from crates.io
cargo install --path . # or from a checkoutThere is also a pre-commit hook, unchanged apart from its language:
repos:
- repo: https://github.com/standard/standard
rev: v17.1.2
hooks:
- id: standardstandard # check every *.js/*.jsx/*.mjs/*.cjs below cwd
standard --fix # fix what can be fixed
standard 'src/**/*.js' # quote globs so the tool expands them, not the shell
standard --stdin < file.js # or `standard -`Or without installing:
cargo run --release --bin standard -- --fixThe CLI prints the same text as the JavaScript original, including --help, and
uses the same exit codes (0 clean, 1 errors present). Problems go to stdout;
the banner and hints go to stderr, so standard 2>/dev/null gives you the
problem list on its own. With --stdin --fix the two swap: the fixed source is
what goes to stdout, and the problems move to stderr prefixed with standard: .
let engine = standard::standard(); // index.js default export
let results = engine.lint_text("var a = 1\n", &standard::LintOptions::default());
assert_eq!(results.error_count, 0);
assert_eq!(results.warning_count, 1); // no-var is a warninglint_text / lint_files mirror standard.lintText / standard.lintFiles, and
LintOptions carries the fields the JavaScript README documented (cwd, fix,
extensions, globals, plugins, envs, parser, use_package_json,
use_git_ignore, ignore, no_default_ignore, no_default_extensions,
filename). extensions and ignore are additive on top of the defaults,
as --ext and --ignore were upstream.
standardrc.json is the descendant of eslintrc.json and carries the same
extends, plus the ignorePatterns an eslintrc file has always been allowed
to hold. Per-project settings are read from the standard key of the nearest
package.json, searching upwards from the working directory:
ignore, globals/global, plugins/plugin, envs/env, parser,
extensions, noDefaultIgnore and noDefaultExtensions.
Ignored by default: **/*.min.js, coverage/**, node_modules/**,
vendor/**, anything beginning with ., and every pattern in the project
root's .gitignore and .git/info/exclude β including ! negations, which put
a file back.
The JavaScript package was 28 lines of glue: it wired standard-engine (CLI and
file selection) to eslint + eslint-config-standard (the rules) through
eslintrc.json. None of those packages exist for Rust, so the port had to
supply them:
| JavaScript | Rust |
|---|---|
index.js |
src/lib.rs (standard()) |
lib/options.js |
src/options.rs |
lib/cli.js + minimist |
src/cli.rs |
bin/cmd.cjs + version-guard |
src/main.rs + src/version_guard.rs + rust-version |
eslintrc.json |
standardrc.json |
standard-engine (dependency) |
src/engine.rs |
eslint + eslint-config-standard (dependencies) |
src/lexer.rs + src/rules.rs |
JSON.parse (built-in) |
src/json.rs |
| glob expansion (dependency) | src/glob.rs |
tools/test-exists.js |
tools/test_exists.rs |
tape tests |
tests/ (built-in harness) |
.npmignore |
exclude in Cargo.toml |
.github/workflows/* (Node matrix) |
the same workflows on a Rust matrix |
.pre-commit-hooks.yaml (language: node) |
the same, language: rust |
The rules are implemented in src/rules.rs, keeping upstream rule ids, message
text and column positions so output can be diffed against the JavaScript
version. Implemented:
quotes, semi, eqeqeq, keyword-spacing, space-before-function-paren,
func-call-spacing, comma-spacing, no-var, no-empty, no-tabs,
no-trailing-spaces, no-multiple-empty-lines, eol-last, indent.
This is 36 of the 196 rules standardrc.json selects β 161 from
eslint-config-standard and 35 more from eslint-config-standard-jsx (see
RULES.md, carried over unchanged).
The boundary is not arbitrary. eslint classifies its own rules, and every rule
in the layout class β the ones decided by where whitespace and punctuation
sit β is implemented here, because that is precisely what a token stream can
decide. The 160 that are missing are the problem and suggestion classes plus
the plugin rules: they need a parsed syntax tree with resolved scopes.
no-unused-vars, no-undef, prefer-const, camelcase, no-redeclare,
brace-style, curly, the 35-rule JSX set and the 14 import/*, n/* and
promise/* plugin rules are among them. Closing that gap means taking a
JavaScript parser as a dependency and rebuilding the rule layer on top of its
AST β a different program from this one. The port does not pretend otherwise: it
reports what it checks and nothing more. --global,
--env, --plugin and --parser are parsed and carried through the options,
but every rule they would affect is in the unimplemented set, so today they
change no output.
The rules that are implemented carry their upstream options, so they agree
with the original on the cases those options govern: eqeqeq skips == null,
no-empty allows an empty catch, no-multiple-empty-lines guards both ends
of the file as well as the middle, and comma-spacing and keyword-spacing
check the space before as well as the one after.
Every one of these follows from the unimplemented rules, and each is measured
rather than assumed β see tests/regressions.rs and the corpus described below.
--fixrewritesvartolet, where the original reachesconst. That last step isprefer-const, one of the 160 unimplemented rules.indenttakes its expectation from leading whitespace rather than from a syntax tree: a line indented by an even but wrong number of spaces is not reported (if (a) {/ four-space body). Tab-indented lines do use block depth, which is what the original's message quotes. The rule never reports a line the original accepts β the approximation only ever misses, it does not invent.- Anything reported by an unimplemented rule is, of course, not reported.
Verified equivalence. Every rule was checked by running the JavaScript original and this port over the same input and diffing, on two corpora: 62 files built to exercise each implemented rule and the tokenizer's awkward corners (regex literals, template substitutions, shebangs, block comments, tagged templates), and 400 real-world JavaScript files taken from a dependency tree.
On the 400 real files, restricted to the rules this port implements:
- 260 agree exactly β same rule id, line, column and message text;
- 140 differ, and 138 of those differ only on
indent(see below); - 2 files differ on anything else: one
block-spacingcolumn inside a 543-column minified line, oneno-tabsposition inside a quoted string.
Every JavaScript file in the original repository (9 files) lints clean under this port, exactly as it does under the original.
cargo test # the internal suite
cargo test --test external_clone -- --ignored # the 245-repo external suiteThe split mirrors npm run test-internal / npm run test-external, and the two
GitHub Actions workflows in .github/workflows/ run the same two halves.
tests/regressions.rs pins behaviour that once diverged from the original β
each test names the upstream rule option or CLI convention it is protecting.
MIT. Copyright (c) Feross Aboukhadijeh.