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

@bam.tech/oxlint-plugin

v0.2.0

Published

oxlint plugin and shareable configs for bam react-native projects

Readme

oxlint plugin by BAM

Shared oxlint rules and configs for new React Native BAM projects. The oxlint counterpart of @bam.tech/eslint-plugin, built so that no ESLint plugin is loaded at lint time: every rule is either an oxlint native (Rust) rule or a rule implemented in this package.

Why

Measured on a 1 240-file React Native tree (median of 5, M-series):

| Configuration | Time | | ----------------------------------------------------------- | ------ | | oxlint, native rules only | 71 ms | | These presets (recommended + import + a11y + tests) | 166 ms | | These presets with --type-aware | 281 ms |

For comparison, the ESLint stack this replaces takes seconds, not milliseconds, on the same tree.

The gap between the first two rows is the price of this package's 9 rules: oxlint's native rules are Rust compiled into the binary, while a rule shipped by a third party runs as JavaScript, and oxlint has no other mechanism for custom rules. That is why the presets lean on native rules wherever one exists, and why adding a rule here should be a last resort (see CONTRIBUTING).

Quick setup

yarn add -D @bam.tech/oxlint-plugin oxlint
# only if you want the type-aware rules
yarn add -D oxlint-tsgolint

Create an oxlint.config.mts in your project root:

import { defineBamConfig } from "@bam.tech/oxlint-plugin/configs";

export default defineBamConfig();

Then lint with oxlint ., or oxlint --type-aware . to include the type-aware rules.

Node 22 or newer. Oxlint does not compile TypeScript itself, it hands the config file to Node and relies on its type-stripping, so an oxlint.config.mts does not load at all on Node 20. On an older runtime use the .oxlintrc.json form shown below instead. (oxlint.config.mjs is not a workaround: oxlint auto-discovers the .mts name, not the .mjs one.)

That is the whole setup. defineBamConfig enables all four presets and, importantly, supplies the ignore patterns (see below). To narrow it down or add project rules:

export default defineBamConfig({
  presets: ["recommended", "tests"],
  rules: { "no-console": "off" },
});

Why the ignore patterns cannot live in a preset

Oxlint matches ignorePatterns gitignore-style, "rooted at the directory containing the configuration file", and files outside that directory cannot be matched. Patterns written inside a published preset would therefore be rooted at the preset's own directory, never at your project, so they can only take effect from your own config. Since oxlint also lints node_modules by default, a config that forgets them lints your dependencies.

defineBamConfig handles this for you. If you write the config by hand, pass them yourself:

import { defineConfig } from "oxlint";
import { ignorePatterns, recommended } from "@bam.tech/oxlint-plugin/configs";

export default defineConfig({ extends: [recommended], ignorePatterns });

Why a config file rather than .oxlintrc.json

extends inside an .oxlintrc.json takes a filesystem path relative to the config file, not a package name. In a monorepo with a hoisted node_modules (yarn workspaces, pnpm) the preset is not at ./node_modules/... from the app directory, so the path breaks. Importing from @bam.tech/oxlint-plugin/configs uses Node resolution and works wherever the package really is.

If you are not in a monorepo and prefer plain JSON, this also works:

{
  "extends": [
    "./node_modules/@bam.tech/oxlint-plugin/configs/recommended.oxlintrc.json",
    "./node_modules/@bam.tech/oxlint-plugin/configs/import.oxlintrc.json",
    "./node_modules/@bam.tech/oxlint-plugin/configs/a11y.oxlintrc.json",
    "./node_modules/@bam.tech/oxlint-plugin/configs/tests.oxlintrc.json"
  ],
  "ignorePatterns": [
    ".cache",
    ".expo-shared",
    ".expo",
    ".yarn",
    "android",
    "ios",
    "coverage",
    "dist",
    "node_modules",
    "expo-env.d.ts"
  ]
}

Shareable configs

| Name | Description | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | recommended | The base config for all projects: the ESLint/TypeScript/React/React-hooks natives, plus this package's React Native rules. Includes the type-aware rules, which only run with --type-aware. | | tests | Test-file rules (native jest, plus the two userEvent rules), scoped to **/*.test.ts?(x). | | import | Import hygiene: native import rules, no-unused-vars, no-unused-imports, consistent-type-imports. | | a11y | React Native accessibility. Enabled by default here, unlike the ESLint plugin where it was opt-in beta. |

Formatting

Formatting is not a lint rule here. eslint-plugin-prettier is replaced by oxfmt, which does the same job natively. Generate the config from your existing Prettier setup:

npx oxfmt --migrate=prettier

The important setting is printWidth: oxfmt defaults to 100 where Prettier defaults to 80, and that single difference accounts for essentially all apparent disagreement between the two. The migration prints a note when it fills the value in for you.

// .oxfmtrc.json
{
  "printWidth": 80,
  "sortPackageJson": false,
  "ignorePatterns": ["dist", "node_modules", "**/*.md"]
}

Markdown is excluded above because this repo already lints it with markdownlint, and oxfmt rewrites fenced code blocks (adding trailing commas to JSON samples, among other things). Drop that pattern if you want oxfmt to own your Markdown too.

Run oxfmt --check . in CI and oxfmt . locally. oxfmt skips node_modules by default and honours .gitignore and .prettierignore, so it needs less configuration than oxlint does.

Conformance, measured: on this repository's 51 TypeScript/JavaScript files, formatting with oxfmt at printWidth: 80 produces byte-identical output to Prettier on all 51, and both flag exactly the same 8 already-unformatted files. This package formats itself with oxfmt in CI.

eslint-plugin-simple-import-sort has no equivalent: oxfmt's sortImports uses its own predefined group order rather than simple-import-sort's regex groups, so enabling it reorders imports once, and there is no export-sorting feature at all.

Rules

All 10 rules ship under the @bam.tech plugin name.

| Rule | Description | Config | Fixable | | ----------------------------------------------- | ---------------------------------------------------------------- | ------------- | ------- | | @bam.tech/no-different-displayname | Enforce component displayName to match the component name | recommended | yes | | @bam.tech/require-named-effect | Enforce named functions inside a useEffect | recommended | | | @bam.tech/no-inline-style-in-array | Disallow inline style objects inside a style array | recommended | | | @bam.tech/no-raw-text | Disallow text outside of a <Text> component | recommended | | | @bam.tech/no-unused-imports | Disallow imports that are never used, and remove them | import | yes | | @bam.tech/await-user-event | Enforce awaiting userEvent calls | tests | yes | | @bam.tech/prefer-user-event | Enforce userEvent over fireEvent | tests | yes | | @bam.tech/has-accessibility-hint | Require an accessibilityHint alongside an accessibilityLabel | a11y | | | @bam.tech/has-valid-accessibility-descriptors | Require accessibility descriptors on touchables and text inputs | a11y | yes | | @bam.tech/has-valid-accessibility-state | Enforce a valid accessibilityState shape | a11y | |

Four of these are reimplementations of rules from eslint-plugin-react-native and eslint-plugin-react-native-a11y (both MIT): no-raw-text and the three a11y rules. Oxlint has no native react-native or react-native-a11y plugin, and oxc has declared new plugins out of scope (oxc#26151), so the only way to keep these checks without loading an ESLint plugin is to own them.

They are checked against the upstream ESLint rules on a shared fixture set and agree on it, with one deliberate difference: prop names are matched case-sensitively. Upstream reaches props through jsx-ast-utils, which defaults to ignoreCase: true because it was written for the DOM, where attribute names really are case-insensitive. React Native props are not: accessibilitylabel is simply not a prop and does nothing at runtime, so an element carrying it genuinely has no accessibility label and is worth reporting. Upstream stays silent on it.

They are also more defensive than the originals. Inputs that make the upstream rules throw (<Pressable accessibilityState />, <View>{`plain`}</View>) are handled here.

Differences from @bam.tech/eslint-plugin

Everything below is a deliberate, measured deviation. On the repo's example-app fixture, ESLint reports 64 findings and this config reports the same findings except as listed here.

Moved to the formatter

| ESLint rule | Replacement | | ---------------------------- | --------------------- | | prettier/prettier | oxfmt | | simple-import-sort/imports | oxfmt sortImports |

Replaced by a native rule

| ESLint rule | oxlint rule | Note | | -------------------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | @bam.tech/no-inline-style | react-perf/jsx-no-new-object-as-prop + @bam.tech/no-inline-style-in-array | The native rule does not look inside arrays; the companion rule covers that. Enabling react-perf/jsx-no-new-array-as-prop instead is not possible, as it rejects the legitimate style={[styles.a, styles.b]}. | | unused-imports/no-unused-imports | no-unused-vars + @bam.tech/no-unused-imports | The native rule reports unused imports, but its import-removing fix is a suggestion, so oxlint --fix skips it. The companion rule carries the same fix as a safe one. See the note below. | | unused-imports/no-unused-vars | no-unused-vars | | | react-hooks/component-hook-factories | react/static-components | Renamed upstream in eslint-plugin-react-hooks 7.1. | | jest/padding-around-all | jest/padding-around-test-blocks + jest/padding-around-after-all-blocks | Covers it/test/describe and afterAll blocks, not every Jest hook the ESLint rule padded (e.g. beforeEach), so this is a partial rather than exact match. |

Dropped

| ESLint rule(s) | Why | | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 15 × testing-library/* | No native equivalent, and a native port is explicitly not planned. The native jest rules do not overlap them: they cover Jest's assertion and hook mechanics, not the Testing Library query API. Reimplementing them was judged disproportionate, as 6 of the 15 need variable-flow tracking. | | simple-import-sort/exports | No native, oxfmt or third-party equivalent exists. Cosmetic. | | react/no-unused-prop-types | Dropped upstream: PropTypes are ignored in React 19, and no-unused-vars plus TypeScript already catch unused props. | | import/no-unresolved | Explicitly not planned upstream: "will always contain false positives due to module resolution complexity." | | no-undef | Only available as a nursery rule. TypeScript already catches undefined identifiers, and typescript-eslint recommends disabling it on TS code. |

Why unused imports need a companion rule

The native no-unused-vars does have an import-removing fix, but it is registered as a suggestion, which oxlint --fix does not apply. The only way to apply it is oxlint --fix-suggestions, and that flag is global: on example-app it also deletes console.log() calls, deletes an if (true) {} block, rewrites an interface ... extends into a type alias and guesses an accessibilityRole onto a <Pressable>. It cannot be narrowed to one rule, so it is not a substitute for the safe, targeted fix that unused-imports/no-unused-imports gave us.

@bam.tech/no-unused-imports carries that fix instead. It removes an unused specifier along with the comma that joined it, drops the braces when no named import survives, and removes the whole statement, trailing newline included, when nothing in it is used. It also removes unused import * as ns bindings, which the native suggestion leaves behind. Side-effect imports (import "./polyfill") declare nothing and are never touched.

A whole block of unused imports is reported as one finding rather than one per import, and so is a run of adjacent unused specifiers inside one { ... }. This is not cosmetic: oxlint --fix applies a single pass of non-touching fixes and does not iterate, so two removals that met at a shared comma or line break would take one oxlint --fix each. Fused into one removal, the block goes in one run. The per-import diagnostic is not lost, since no-unused-vars still reports each binding separately.

Two further deliberate consequences:

  • Unused imports are reported twice, once by no-unused-vars and once by this rule. The ESLint config did the same, from @typescript-eslint/no-unused-vars and unused-imports/no-unused-imports.
  • _-prefixed imports are ignored, matching the native no-unused-vars, which treats ^_ as the deliberately-unused convention. Reporting them here would make oxlint --fix rewrite code that oxlint itself calls clean.

Known rule-semantics divergences

  • array-callback-return is enabled and native, but oxlint's implementation does not flag Array.prototype.reduce() with no return value, which ESLint's does. Verified on example-app/eslint-breaking-examples/break-array-callback-return-rule.ts.

Type-aware rules need a modern moduleResolution

no-floating-promises, no-unnecessary-condition and return-await are in recommended, but they only do anything when you pass --type-aware, which needs the optional oxlint-tsgolint dependency. tsgolint rejects the legacy "moduleResolution": "node" (node10) outright:

Option 'moduleResolution=node10' has been removed. Please remove it from your configuration.

Expo sets "moduleResolution": "bundler" itself from SDK 53 onwards, so a current project needs nothing. On older Expo, override it in your own tsconfig.json:

{
  "extends": "@bam.tech/typescript-config",
  "compilerOptions": { "moduleResolution": "bundler" }
}

This is worth doing regardless of linting: under node10 resolution TypeScript cannot read the exports map that most current packages ship their types behind, and reports Cannot find module ... There are types at ..., but this result could not be resolved under your current 'moduleResolution' setting. Metro honours those exports maps from React Native 0.79 (Expo 53), so node10 also disagrees with what the bundler actually does at runtime.

Without --type-aware these three rules are inert and the rest of the config works normally.

Migrating an existing project

The cutover is atomic, not incremental: oxlint matches suppression comments on the plugin-qualified rule name, so every // eslint-disable-next-line @bam.tech/foo has to be rewritten, and once it is, ESLint no longer recognises the rule. Plan a single change, not a gradual one.

npx @oxlint/migrate eslint.config.js gets a project most of the way, but read Differences first: it will happily carry prettier/prettier across and keep running Prettier inside the linter.

Contribute