Skip to content
Β 
Β 

Latest commit

Β 

History

2,002 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

standard β€” Rust port

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.

Install

npm install standard becomes:

cargo install standard          # from crates.io
cargo install --path .          # or from a checkout

There is also a pre-commit hook, unchanged apart from its language:

repos:
  - repo: https://github.com/standard/standard
    rev: v17.1.2
    hooks:
      - id: standard

Usage

standard                        # 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 -- --fix

The 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: .

Library

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 warning

lint_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.

Configuration

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.

What moved, and what had to be rebuilt

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

Rule coverage

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.

Known differences

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.

  • --fix rewrites var to let, where the original reaches const. That last step is prefer-const, one of the 160 unimplemented rules.
  • indent takes 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-spacing column inside a 543-column minified line, one no-tabs position 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.

Tests

cargo test                                    # the internal suite
cargo test --test external_clone -- --ignored # the 245-repo external suite

The 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.

License

MIT. Copyright (c) Feross Aboukhadijeh.

About

🌟 JavaScript Style Guide, with linter & automatic code fixer

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages