eslint-plugin-nitro-modules
v0.2.2
Published
ESLint plugin for enforcing best practices when developing with [Nitro](https://nitro.margelo.com) - a framework for building cross-platform modules with Swift, Kotlin, and C++.
Readme
eslint-plugin-nitro-modules
ESLint plugin for enforcing best practices when developing with Nitro - a framework for building cross-platform modules with Swift, Kotlin, and C++.
Installation
Install the package with your preferred package manager:
bun add -D eslint-plugin-nitro-modules
# or
npm install --save-dev eslint-plugin-nitro-modules
# or
yarn add -D eslint-plugin-nitro-modules
# or
pnpm add -D eslint-plugin-nitro-modulesUsage
This plugin is authored for ESLint 9’s flat config. Add it to eslint.config.js (or eslint.config.mjs):
import { defineConfig } from "eslint/config";
import nitroModulesPlugin from "eslint-plugin-nitro-modules";
export default defineConfig([
// Start from the preset (includes plugin registration)
nitroModulesPlugin.configs.recommended,
// Optional: override specific rules (dotted rule keys)
{
plugins: {
"nitro-modules-eslint": nitroModulesPlugin,
},
rules: {
"nitro-modules-eslint/require-hybrid-object-platform-lang": "error",
"nitro-modules-eslint/require-hybrid-view-platform-lang": "error",
"nitro-modules-eslint/require-callback-wrapper-for-view-callbacks": "error",
"nitro-modules-eslint/no-untyped-map": "warn",
"nitro-modules-eslint/no-variant": "warn",
"nitro-modules-eslint/no-object-param": "warn",
"nitro-modules-eslint/prefer-arraybuffer-for-binary": "warn",
"nitro-modules-eslint/no-unknown-nitro-type": "error",
},
},
]);Available Rules
| Rule | Recommended | Strict | What it enforces |
| --------------------------------------------- | ----------- | ------ | -------------------------------------------------------------------------------------------- |
| require-hybrid-object-platform-lang | error | error | HybridObject must declare a valid platform language config. |
| require-hybrid-view-platform-lang | error | error | HybridView must declare type params + a valid platform language config. |
| require-callback-wrapper-for-view-callbacks | error | error | Nitro HostComponent callback props must be wrapped with callback(...) (not raw functions). |
| no-untyped-map | warn | error | Avoid untyped AnyMap in Nitro APIs to reduce allocations. |
| no-variant | warn | error | Avoid Nitro variant APIs; prefer strongly typed interfaces. |
| no-object-param | warn | error | Avoid “bag object” parameters; flatten params to reduce allocations. |
| prefer-arraybuffer-for-binary | warn | error | Avoid number[] for binary data; Nitro binary data should use ArrayBuffer. |
| no-unknown-nitro-type | error | error | Disallow types not supported by Nitro’s typing system in Nitro API method params/returns. |
Rule Details
require-hybrid-object-platform-lang (Error)
Enforces that HybridObject interfaces specify a platform language configuration for at least one platform. The config is an object literal with ios and/or android keys.
- iOS allowed values:
'swift'or'c++' - Android allowed values:
'kotlin'or'c++' - If you specify both platforms, the only valid combinations are:
{ ios: 'swift', android: 'kotlin' }{ ios: 'c++', android: 'c++' }
Bad:
export interface BadSpec extends HybridObject {
// ...
}Good:
export interface GoodSpec extends HybridObject<{ ios: "swift"; android: "kotlin" }> {
// ...
}no-unknown-nitro-type (Error)
Enforces that Nitro API method parameter and return types are part of Nitro’s supported typing system, or are locally-defined custom types (interfaces, enums, type aliases).
Bad:
export interface BadSpec extends HybridObject<{ ios: "swift" }> {
// Map/Set/Uint8Array/inline object types are not supported Nitro types
bad1(x: Map<string, number>): void;
bad2(x: { a: number }): void;
bad3(): Uint8Array;
}Good:
export interface GoodSpec extends HybridObject<{ ios: "swift" }> {
ok1(x: Record<string, number>): void;
ok2(): ArrayBuffer;
}require-hybrid-view-platform-lang (Error)
Enforces that HybridView type aliases specify all three type parameters and a valid platform language configuration:
- Props: first type parameter; must be an interface extending
HybridViewProps(no inline object types) - Methods: second type parameter; must be an interface extending
HybridViewMethods(no inline object types) - Platform config: third type parameter; object with
iosand/orandroidkeys - Allowed values: iOS
'swift', Android'kotlin' - Valid combination when both platforms specified:
{ ios: 'swift', android: 'kotlin' }
Bad:
export type BadView = HybridView<
{ enabled: boolean }, // inline object type (disallowed)
{ onTap(): void }, // inline object type (disallowed)
{ ios: "swift"; android: "kotlin" }
>;Good:
export interface GoodViewProps extends HybridViewProps {
enabled: boolean;
}
export interface GoodViewMethods extends HybridViewMethods {
onTap(): void;
}
export type GoodView = HybridView<
GoodViewProps,
GoodViewMethods,
{ ios: "swift"; android: "kotlin" }
>;no-untyped-map (Error)
Prevents using untyped AnyMap objects in Nitro APIs to avoid extra allocations. Use typed maps or alternatives.
Bad:
export interface Spec extends HybridObject<{ ios: "swift"; android: "kotlin" }> {
// AnyMap is a dynamic map type (discouraged)
getConfig(): AnyMap;
}Good:
export interface Config {
enabled: boolean;
maxRetries: number;
}
export interface Spec extends HybridObject<{ ios: "swift"; android: "kotlin" }> {
getConfig(): Config;
}no-variant (Warning/Error)
Discourages using variant APIs in Nitro. Prefer strongly-typed interfaces.
Bad:
export interface Spec extends HybridObject<{ ios: "swift"; android: "kotlin" }> {
setValue(value: string | number): void;
}Good:
export interface Spec extends HybridObject<{ ios: "swift"; android: "kotlin" }> {
setStringValue(value: string): void;
setNumberValue(value: number): void;
}no-object-param (Warning/Error)
Discourages single bag-object parameters in Nitro API methods. Flatten parameters to avoid extra native allocations.
Bad:
export interface Spec extends HybridObject<{ ios: "swift"; android: "kotlin" }> {
make(payload: { id: string; size: number }): void;
}Good:
export interface Spec extends HybridObject<{ ios: "swift"; android: "kotlin" }> {
make(id: string, size: number): void;
}prefer-arraybuffer-for-binary (Warning/Error)
Prevents using number[] for binary data in Nitro APIs (to minimize copying/allocations). Nitro binary data should use ArrayBuffer.
Bad:
export interface Spec extends HybridObject<{ ios: "swift"; android: "kotlin" }> {
// number[] forces extra copying/allocations
encode(data: number[]): ArrayBuffer;
}Good:
export interface Spec extends HybridObject<{ ios: "swift"; android: "kotlin" }> {
encode(data: ArrayBuffer): ArrayBuffer;
}require-callback-wrapper-for-view-callbacks (Error)
Enforces that callback props for Nitro HostComponents created with getHostComponent(...) are wrapped using callback(...) (instead of passing raw functions).
Bad:
// Given: const NitroView = getHostComponent<...>("NitroView")
<NitroView onReady={() => {}} />Good:
import { callback } from "react-native-nitro-modules";
// Given: const NitroView = getHostComponent<...>("NitroView")
<NitroView onReady={callback(() => {})} />;Development
Prerequisites
- Bun v1.3.0 or higher
- TypeScript
Getting Started
- Clone the repository
- Install dependencies:
bun install- Build the project:
bun run build- Type check the project:
bun run typecheckProject Structure
eslint-plugin-nitro-modules/
├── src/
│ ├── index.ts # Main plugin entry point
│ ├── utils.ts # Shared utilities
│ └── rules/
│ ├── _rule-creator.ts # Rule creator helper
│ ├── no-object-param.ts
│ ├── no-untyped-map.ts
│ ├── no-variant.ts
│ ├── prefer-arraybuffer-for-binary.ts
│ ├── require-callback-wrapper-for-view-callbacks.ts
│ ├── require-hybrid-object-platform-lang.ts
│ └── require-hybrid-view-platform-lang.ts
├── tests/ # Test files for each rule
└── dist/ # Compiled output (generated)Running Tests
Tests use Bun's built-in test runner and TypeScript ESLint's RuleTester:
bun testAdding a New Rule
- Create a new rule file in
src/rules/usingcreateNitroRulehelper - Export the rule from
src/index.ts - Add the rule to the recommended and strict configs
- Create a test file in
tests/ - Run tests:
bun test
Example rule skeleton:
import { createNitroRule } from "./_rule-creator";
export default createNitroRule<[], "messageId">({
name: "your-rule-name",
meta: {
type: "suggestion",
docs: {
description: "Rule description",
url: "https://nitro.margelo.com/docs/...",
},
messages: {
messageId: "Your error message",
},
schema: [],
},
defaultOptions: [],
create(ctx) {
return {
// AST visitor methods
};
},
});Writing Tests
Use TypeScript ESLint's RuleTester. Each test case must include a name and code:
import { ruleTester } from "./ruleTester";
import rule from "../src/rules/your-rule";
ruleTester.run("your-rule", rule, {
valid: [
{
name: "short descriptive test name",
code: `
// Your valid TypeScript code here
interface MyInterface extends HybridObject<{ ios: 'swift', android: 'kotlin' }> {
someMethod(): void;
}
`,
},
],
invalid: [
{
name: "short descriptive error test name",
code: `
// Your invalid TypeScript code here
interface BadInterface {
badMethod(options: object): void;
}
`,
errors: [{ messageId: "yourMessageId" }],
},
],
});Important: Always include descriptive name fields for each test case to make test failures easier to identify.
Contributing
Contributions are welcome — please see CONTRIBUTING.md.
License
This project is open source and available under the MIT License.
