npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

eslint-parser-oxc

v0.1.1

Published

ESLint custom parser backed by oxc-parser. Parses JS, JSX, TS and TSX for syntax-only rules with no `typescript` dependency.

Readme

eslint-parser-oxc

An ESLint 9/10 custom parser backed by oxc-parser, the Rust-based Oxc parser. It parses JavaScript, JSX, TypeScript and TSX for syntax-only rules, and it does not depend on typescript at runtime.

That last part is the point. TypeScript 7 ships no JavaScript API, so @typescript-eslint/parser cannot run in a repo that has moved to it. The usual escape hatch is @babel/eslint-parser, which works but is a JavaScript parser with a Babel-AST-to-ESTree conversion layer and drags in @babel/core. This is a thin adapter over a parser that already emits an ESTree/TS-ESTree shaped AST.

npm install --save-dev eslint-parser-oxc

Requires Node 20.19+, 22.12+ or 24+, and ESLint 9 or 10 as a peer.

Runtime dependencies: oxc-parser, @typescript-eslint/scope-manager, @typescript-eslint/visitor-keys. None of them load typescript — the @typescript-eslint packages import it only in .d.ts files, and npm run verify:packaged proves it by installing the packed tarball into a project with no typescript on disk and linting a .tsx file with it.

@typescript-eslint/scope-manager handles JavaScript files too, rather than falling back to eslint-scope for them. It is a superset — a fork of eslint-scope with TypeScript support added — so using it everywhere means one code path, one dependency fewer, and JavaScript results that are identical to typescript-eslint's for the same reason the TypeScript ones are.

Usage

// eslint.config.mjs
import * as oxcParser from 'eslint-parser-oxc';

export default [
  {
    files: ['**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx}'],
    languageOptions: {
      parser: oxcParser,
      ecmaVersion: 'latest',
      sourceType: 'module',
    },
  },
];

That is the whole setup. The language — and in particular whether JSX is on — is taken from the file extension, so .ts and .tsx do not need separate config blocks the way a Babel setup does.

With eslint-plugin-import-x

import-x parses the files an import points at, and needs to be told which parser handles which extensions:

import * as oxcParser from 'eslint-parser-oxc';
import importX from 'eslint-plugin-import-x';

export default [
  {
    files: ['**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx}'],
    languageOptions: { parser: oxcParser, ecmaVersion: 'latest', sourceType: 'module' },
    settings: {
      'import-x/parsers': {
        'eslint-parser-oxc': ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'],
      },
      'import-x/extensions': ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'],
      'import-x/resolver': { typescript: true },
    },
    plugins: { 'import-x': importX },
    rules: importX.flatConfigs.recommended.rules,
  },
];

The same key works for eslint-plugin-import's import/parsers.

CommonJS

Flat config loads parsers with import, but a CJS build is published for jiti and for require-based configs:

const oxcParser = require('eslint-parser-oxc');

Options

Everything goes under languageOptions.parserOptions.

| Option | Default | Effect | | --- | --- | --- | | lang | from the file extension | 'js', 'jsx', 'ts', 'tsx' or 'dts'. Overrides everything else. | | sourceType | 'module', or 'commonjs' for .cjs | 'script', 'module', 'commonjs' or 'unambiguous'. Passed through to oxc; 'commonjs' is reported as 'script' on the AST, which is all ESLint understands. Under ESLint the default never applies — ESLint always supplies languageOptions.sourceType, so set it there for .cjs files. | | ecmaFeatures.jsx | from the file extension | Forces JSX on or off. Ignored when lang is set. | | ecmaFeatures.globalReturn | false | Allows a top-level return, by parsing as CommonJS. | | jsxPragma | 'React' | Identifier marked as referenced by every JSX element, so no-unused-vars does not flag import React. null for the automatic runtime. | | jsxFragmentName | null | As above, for fragments. | | lib | ['esnext'] | TypeScript lib names, used by the scope manager to predeclare ambient type names. | | preserveParens | false | Leave this alone. ESLint's AST contract has no ParenthesizedExpression. | | showSemanticErrors | false | Also report oxc's scope- and symbol-level diagnostics as parse errors. | | ecmaVersion | — | Accepted and ignored; oxc always parses the latest syntax. |

Language selection

| Extension | lang | JSX | | --- | --- | --- | | .js .mjs .cjs | js | off | | .jsx | jsx | on | | .ts .mts .cts | ts | off | | .tsx | tsx | on | | .d.ts .d.mts .d.cts | dts | off | | anything else | tsx | on |

JSX is off for .ts on purpose: with it on, <T>value parses as a JSX element instead of a type assertion, and const f = <T,>() => … changes meaning. TypeScript itself makes the same extension-based split.

Compatibility

| | eslint-parser-oxc | @typescript-eslint/parser | @babel/eslint-parser | | --- | --- | --- | --- | | Runs without typescript installed | yes | no | yes | | AST | TS-ESTree | TS-ESTree | Babel AST converted to ESTree | | scopeManager | @typescript-eslint/scope-manager | @typescript-eslint/scope-manager | eslint-scope, patched | | visitorKeys | @typescript-eslint/visitor-keys | @typescript-eslint/visitor-keys | Babel's | | Type-aware rules | no | yes | no | | parserServices.program | no | yes | no | | JSX on for .ts | no (correct) | no | yes unless configured per-extension | | Core rules, import-x, react, jsx-a11y | identical findings (verified) | baseline | mostly, with known AST gaps | | Runtime dependencies | 3 | 6, incl. typescript | @babel/core and its tree | | Extra start/end on nodes | yes | no | yes |

Where the ASTs differ at all, it is written down: see DIVERGENCES.md. The short version is that the AST, ranges, locs, comments and tokens are identical to typescript-eslint's, with five documented exceptions, two of which are cases where this parser matches espree — ESLint's own parser — and typescript-eslint does not.

Limitations

  • No type information. services is {}. Type-aware rules (@typescript-eslint/no-floating-promises and friends) will not run. Use oxlint-tsgolint for those.
  • ecmaVersion is ignored. oxc always parses the latest syntax, so this parser will not turn newer syntax into a syntax error for you.
  • Stricter than TypeScript's parser. TypeScript recovers from most errors and reports them from the compiler instead; oxc reports them at parse time, so ESLint sees a fatal parse error. In a 3,124-file corpus this affected 38 files, every one of them code TypeScript itself rejects. The list and the classes are in DIVERGENCES.md.
  • No Flow, Vue SFCs, Svelte or MDX.
  • oxc-parser is pinned to an exact version and should stay that way. Its ESTree output is still moving; the conformance suite is what says whether a bump is safe.

Verification

The claim "identical to typescript-eslint" is a test, not an opinion. The reference parser lives in conformance/reference/, its own npm install with [email protected], so the package under test is always exercised in a tree with no typescript in it.

npm run build
npm run conformance:corpus     # fetch the external corpora (~45 MB)
npm run conformance:snapshot   # parse everything with the reference
npm run conformance            # diff the ASTs
npm run conformance:lint       # diff the ESLint findings, and --fix output
npm run verify:packaged        # install the tarball where typescript is absent
npm test                       # unit tests

The corpus is typescript-eslint's ast-spec fixtures (one file per syntactic form), ESLint's lib and parsing fixtures, and four real repositories — TanStack Query, Vite, redux-saga and typescript-eslint itself — pinned to exact tags. Point the suite at your own monorepo with node conformance/reference/snapshot.mjs <dir> conformance/.snapshots-mine followed by node conformance/run.mjs --snapshots conformance/.snapshots-mine.

Current results, [email protected] against @typescript-eslint/[email protected] on [email protected]:

| Check | Result | | --- | --- | | ASTs identical (nodes, ranges, locs, comments, tokens) | 2754 / 2754 comparable files | | ESLint findings identical | 0 differences across 17,050 findings | | --fix output byte-identical | 0 files differ | | Scope analysis on TS-only constructs | 17 cases, all matching the reference | | Files rejected here and accepted by the reference | 38, all reviewed and checked in |

The rule set is ESLint core recommended plus eslint-plugin-react, eslint-plugin-import-x and eslint-plugin-jsx-a11y recommended, with the resolution-dependent import-x rules off. --fix adds prefer-const, no-var, object-shorthand, dot-notation, arrow-body-style, no-extra-semi, import-x/order and import-x/newline-after-import.

Benchmark

bench/run.mjs times parsing and a full lint pass separately, because rule execution dominates a real lint run — a parser that is twice as fast only moves its own share of the wall clock.

3,124 files / 17.2 MB (the conformance corpus), Node 24, Apple M-series, median of five parse passes and three lint passes after warmup:

| Parser | Parse | Per file | Throughput | Full lint | | --- | --- | --- | --- | --- | | eslint-parser-oxc | 1,792 ms | 0.57 ms | 9.6 MB/s | 6,477 ms | | @babel/eslint-parser | 3,527 ms | 1.13 ms | 4.9 MB/s | 8,670 ms | | @typescript-eslint/parser (TypeScript 6) | 3,745 ms | 1.20 ms | 4.6 MB/s | 8,693 ms |

So parsing is about 2× faster than either alternative, and a full lint of this corpus with the same rule set is about 25% faster end to end. Parsing is roughly 28% of the lint run here rather than 43%.

Two caveats on the parse column. It includes the work oxc does not do itself — attaching loc, tokenizing, decoding JSX entities — so it is a fair apples-to-apples number for what ESLint receives, not a measurement of the Rust parser alone. And the three parsers reject slightly different files (359, 448 and 332 of 3,124 respectively), so each is doing marginally different work.

Run it yourself with npm run bench -- conformance/.corpus.

Development

npm install
npm run build       # tsdown, ESM + CJS + types
npm run typecheck
npm test

src/ is small and each file has one job:

| File | Job | | --- | --- | | index.ts | parseForESLint, parse, meta | | options.ts | file extension and parserOptions to oxc options | | line-table.ts | offset to line/column | | ast.ts | one traversal: attach loc, collect tokenizer hints, repair ranges | | tokenizer.ts | the token stream | | jsx-entities.ts, html-entities.ts | JSX entity decoding | | normalize.ts | hashbang and sourceType reconciliation | | errors.ts | oxc diagnostics to the error shape ESLint reads |

When a conformance run reports a token mismatch, node conformance/tokens.mjs <file> shows the first few side by side with their source context, and node conformance/reference/probe.mjs '[["case", "code", false]]' prints what the reference parser makes of an arbitrary snippet.

Releasing

Releases are published from CI via trusted publishing — there is no npm token anywhere in this repository — and every tarball carries a provenance attestation tying it to the commit and workflow run that built it.

Publishing a GitHub release triggers .github/workflows/release.yml:

npm version patch          # or minor / major — bumps package.json and commits
git push --follow-tags
gh release create v0.1.1 --generate-notes

The workflow verifies the release tag against package.json, runs the full differential suite against the reference parser, and then stages the release rather than publishing it. Staging is deliberate: the trusted publisher is configured stage-only, so a compromised workflow run cannot put anything on the registry by itself. A maintainer approves the tarball with 2FA:

npm stage list eslint-parser-oxc
npm stage view <stage-id>       # inspect what would go live
npm stage approve <stage-id>

To rehearse, run the Release workflow manually from the Actions tab; dry_run defaults to true. It only works against a version not yet on the registry, so bump package.json first.

A release marked as a prerelease is staged under the next dist-tag, so it never becomes what a plain npm install eslint-parser-oxc resolves to.

The release gate is deliberately stricter than the pull-request gate: this parser's contract is that its output is identical to typescript-eslint's, so that is what has to hold before a version ships.

License

MIT — see LICENSE.

The HTML entity table in src/html-entities.ts is derived from TypeScript (Apache-2.0); the differential suite downloads source from five other projects at test time without vendoring any of it. Both are covered in THIRD-PARTY-NOTICES.md.

Contributions are accepted under the same MIT license as the project.