|
| 1 | +# React Native support |
| 2 | + |
| 3 | +## What ships today |
| 4 | + |
| 5 | +The `reactNative` preset (`eslint/react-native.js`, exported as |
| 6 | +`@zoldytech/javascript/eslint/react-native`) is the `react` preset's foundation |
| 7 | +applied to React Native: |
| 8 | + |
| 9 | +- **antfu `react: true`** — `@eslint-react` + `react-refresh` + `react-hooks` |
| 10 | + (`react/exhaustive-deps`, `react/rules-of-hooks`, Fast-Refresh checks). These |
| 11 | + are React-semantic, not DOM-specific, so they apply to RN unchanged. |
| 12 | +- **The full SonarQube layer** (`sonarLayer` + `sonarReactRules` + `sonarTestOff`). |
| 13 | + React Native is just React + TS/JS; SonarQube itself ships **no** RN-specific |
| 14 | + rules, so this is full SonarQube parity for RN code. |
| 15 | +- **RN runtime globals** (`__DEV__`, `HermesInternal`) so they don't trip |
| 16 | + `no-undef`. antfu already provides the browser + node globals RN relies on |
| 17 | + (`fetch`, `navigator`, `process`, `require`, timers, …). |
| 18 | +- **`tsconfig/react-native.json`** — extends base, `jsx: react-jsx`, includes the |
| 19 | + DOM lib (see [react-native-web](#react-native-web) below). |
| 20 | + |
| 21 | +Same option shape as every other preset (`typeChecked`, `tsconfigPath`, `tsdoc`, |
| 22 | +`ignores`, `testGlobs`, `overrides`, `antfuOptions`). |
| 23 | + |
| 24 | +## react-native-web |
| 25 | + |
| 26 | +Universal codebases that also target the web via |
| 27 | +[`react-native-web`](https://necolas.github.io/react-native-web/) are supported |
| 28 | +with no extra ESLint config: |
| 29 | + |
| 30 | +- **Linting is identical.** RNW runs in the browser, so antfu's `browser` globals |
| 31 | + already cover the web APIs, `__DEV__` is declared by the preset, and web-only |
| 32 | + files (`Foo.web.tsx`, `Foo.web.ts`) already match the preset's globs. The same |
| 33 | + React rules apply on both platforms. Nothing platform-specific to wire in. |
| 34 | +- **TypeScript.** `tsconfig/react-native.json` deliberately keeps the **DOM lib** |
| 35 | + (`["ES2022", "DOM", "DOM.Iterable"]`) so web-targeted and web-only code |
| 36 | + type-checks — TypeScript can't vary `lib` per file, so the web target sets the |
| 37 | + floor. RNW ships augmented type definitions; install `@types/react-native-web` |
| 38 | + and, if you want the web-augmented surface, add |
| 39 | + `"types": ["react-native-web"]` in your project `tsconfig.json`. |
| 40 | + |
| 41 | + ```json |
| 42 | + { |
| 43 | + "extends": "@zoldytech/javascript/tsconfig/react-native.json", |
| 44 | + "compilerOptions": { "types": ["react-native-web"] }, |
| 45 | + "include": ["src"] |
| 46 | + } |
| 47 | + ``` |
| 48 | + |
| 49 | + **Native-only** projects (no web target) can drop the DOM lib to catch |
| 50 | + accidental DOM usage on native, by overriding `lib` in their own tsconfig: |
| 51 | + `"compilerOptions": { "lib": ["ES2022"] }`. |
| 52 | +- The bundler alias (`react-native$` → `react-native-web`) is a build-tool concern |
| 53 | + (webpack/Metro/Vite), out of scope for this package. |
| 54 | + |
| 55 | +## Deferred: RN-specific style rules (blocked on ESLint 10) |
| 56 | + |
| 57 | +The RN-idiom lint rules — `no-inline-styles`, `no-color-literals`, |
| 58 | +`no-unused-styles`, `split-platform-components`, `no-single-element-style-arrays`, |
| 59 | +`no-raw-text`, `sort-styles` — live only in |
| 60 | +[`eslint-plugin-react-native`](https://github.com/intellicode/eslint-plugin-react-native). |
| 61 | +They are **not** wired in yet because that plugin is incompatible with ESLint 10, |
| 62 | +which this package requires (peer `eslint >=10.4`). |
| 63 | + |
| 64 | +**Verified against ESLint 10.7 (2026-07):** |
| 65 | + |
| 66 | +| Rule | Status on ESLint 10 | |
| 67 | +| --- | --- | |
| 68 | +| `no-unused-styles`, `no-inline-styles`, `no-color-literals`, `sort-styles` | **crash** — call `context.getSourceCode()` (removed in ESLint 10) | |
| 69 | +| `split-platform-components` | **crash** — calls `context.getFilename()` (removed in ESLint 10) | |
| 70 | +| `no-raw-text`, `no-single-element-style-arrays` | load but are the least useful rules | |
| 71 | + |
| 72 | +`eslint-plugin-react-native@5.0.0` is the latest release and declares peer |
| 73 | +`eslint ^3 … ^9`. `@react-native/eslint-plugin` ships no style rules |
| 74 | +(`platform-colors`, `no-deep-imports` only). `@react-native/eslint-config` peers |
| 75 | +`eslint ^8 || ^9` and pulls in `eslint-plugin-react`, which this package |
| 76 | +deliberately avoids (see `eslint/next.js` header). |
| 77 | + |
| 78 | +Registering the raw rule objects ourselves does **not** work around this: the |
| 79 | +rule bodies themselves call the removed APIs. |
| 80 | + |
| 81 | +## Plan for adding the style rules |
| 82 | + |
| 83 | +When `eslint-plugin-react-native` publishes an ESLint-10-compatible release (drops |
| 84 | +`context.getSourceCode()`/`getFilename()`, widens its `eslint` peer range): |
| 85 | + |
| 86 | +1. Add it as a dependency (`npm i -D eslint-plugin-react-native`) and confirm |
| 87 | + `npm install` resolves cleanly against `eslint >=10.4` (no `--legacy-peer-deps`). |
| 88 | +2. In `eslint/react-native.js`, extend `reactNativeGlobals()` into a full |
| 89 | + `reactNativeLayer()` block that also registers the plugin and its rules |
| 90 | + (mirror `nextCoreWebVitals()` in `eslint/next.js`): |
| 91 | + |
| 92 | + ```js |
| 93 | + import reactNativePlugin from 'eslint-plugin-react-native'; |
| 94 | + |
| 95 | + // Replace reactNativeGlobals() with a full layer block that also registers |
| 96 | + // the plugin (mirror nextCoreWebVitals()): |
| 97 | + const reactNativeLayer = { |
| 98 | + name: 'zoldytech/react-native', |
| 99 | + files: ['**/*.{js,jsx,ts,tsx}'], |
| 100 | + plugins: { 'react-native': reactNativePlugin }, |
| 101 | + // the plugin exposes the RN env globals directly: |
| 102 | + languageOptions: { |
| 103 | + globals: { ...reactNativePlugin.environments['react-native'].globals }, |
| 104 | + }, |
| 105 | + rules: { |
| 106 | + 'react-native/no-unused-styles': 'error', |
| 107 | + 'react-native/no-single-element-style-arrays': 'error', |
| 108 | + 'react-native/split-platform-components': 'error', |
| 109 | + 'react-native/no-inline-styles': 'error', |
| 110 | + 'react-native/no-color-literals': 'error', |
| 111 | + 'react-native/no-raw-text': 'error', // noisiest — relax via `overrides` if needed |
| 112 | + }, |
| 113 | + }; |
| 114 | + ``` |
| 115 | + |
| 116 | + (Omit `sort-styles` — pure ordering, Prettier's domain.) |
| 117 | +3. Add a `dirty.tsx` case to `test/fixtures/react-native/` that trips a couple of |
| 118 | + the style rules and extend the `presetSuite('react-native', …)` expected-rules |
| 119 | + list in `test/presets.test.js` accordingly. |
| 120 | +4. Verify with `npm test` and a manual lint of a snippet with an inline style, a |
| 121 | + color literal, and an unused `StyleSheet` entry. |
| 122 | + |
| 123 | +Track upstream: <https://github.com/intellicode/eslint-plugin-react-native/issues> |
| 124 | +(ESLint 10 / flat-config support). |
0 commit comments