Skip to content

Commit c2153f0

Browse files
committed
chore(lint): consolidate eslint and prettier into the root workspace
Move all ESLint/Prettier deps and one shared flat config to the repo root so every workspace uses the same rule set. The three sub-workspace eslint.config.mjs files (core, SampleApp, TypeScriptMessaging) are removed; flat-config auto-discovery walks up from any cwd. Root `lint`/`lint-fix` now run in place instead of proxying to core, and `validate-translations` is still invoked via `yarn workspace stream-chat-react-native-core`. The shared config switches from the legacy `@react-native-community/eslint-*` family to the modern `@react-native/eslint-config` + plugin and applies the same strictness across `package/**` and `examples/**`. `import/no-unresolved` is disabled repo-wide because the legacy node resolver doesn't understand Yarn 4 workspace hoisting or TS path aliases - TypeScript already catches these. Bump the laggard plugins while keeping ESLint on 9 and @react-native/eslint-config on 0.81.6: - eslint-plugin-jest 28 -> 29 - eslint-plugin-react-hooks 5 -> 7 - eslint-plugin-prettier 5.4.1 -> 5.5.5 - eslint-config-prettier 10.1.5 -> 10.1.8 Also include the manual @commitlint/* major bump (12 -> 21) and refreshed eslint/typescript-eslint/prettier floors. Extend `.prettierignore` to exclude native scaffolding, `.github/`, `.claude/`, `ai-docs/`, top-level docs and CHANGELOGs. The latent issues this surfaced in example-app code are addressed with targeted per-line `eslint-disable-next-line` comments rather than relaxing rules globally. CI (`yarn lint` in check-pr.yml / release.yml) and the pre-commit hook keep working without changes.
1 parent f4aded6 commit c2153f0

159 files changed

Lines changed: 1388 additions & 2492 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Consolidate ESLint + Prettier into the root workspace
2+
3+
## Context
4+
5+
Today every workspace in the monorepo carries its own lint tooling:
6+
7+
- `package/eslint.config.mjs` (full SDK config, uses the legacy `@react-native-community/eslint-plugin@1.3.0` and `@react-native-community/eslint-config@3.2.0`, integrates Prettier via `eslint-plugin-prettier`, lints Markdown).
8+
- `examples/SampleApp/eslint.config.mjs` (lightweight, uses modern `@react-native/eslint-config@0.81.6` — only `jsx-quotes` + `no-inline-styles` customizations).
9+
- `examples/TypeScriptMessaging/eslint.config.mjs` (same lightweight shape, modern `@react-native/eslint-config@0.80.2`).
10+
- `examples/ExpoMessaging`, `package/native-package`, `package/expo-package` — no ESLint config, no scripts.
11+
- Every workspace except the three smallest declares its own copy of `eslint`, `typescript-eslint`, `prettier`, and the React/RN plugins. There is **version drift** (`prettier ^3.5.1` at root vs `^3.5.3` everywhere else; two different RN ESLint config families).
12+
13+
Prettier is already nearly centralized — `.prettierrc` and `.prettierignore` live at the repo root and are referenced by core via `../.prettierrc`. Only the `prettier` binary itself is duplicated.
14+
15+
CI (`.github/workflows/check-pr.yml:33`, `.github/workflows/release.yml:48`) and the pre-commit hook (`dotgit/hooks/pre-commit-format.sh:5-6`) both invoke `yarn lint` at the repo root, which today proxies to `yarn workspace stream-chat-react-native-core lint`.
16+
17+
**Goal:** one shared ESLint config and one Prettier setup, both owned by the root workspace. Sub-workspaces declare zero eslint/prettier dependencies, ship no config files, and the existing `yarn lint` / `yarn lint-fix` / pre-commit hook / CI entry points keep working unchanged.
18+
19+
**User decisions captured in this plan:**
20+
21+
1. Standardize on the modern **`@react-native/eslint-config`** (single version) and drop `@react-native-community/eslint-*`.
22+
2. **Same strictness everywhere** — one shared rule set applied to `package/**` and `examples/**` alike. Expect a one-time wave of fixes/disables in the example apps.
23+
3. **Keep the current Prettier integration**: `eslint-plugin-prettier` + `eslint-config-prettier` inside the ESLint config, and a separate `prettier --list-different` step in the lint script.
24+
25+
## Approach
26+
27+
- Add **one** `eslint.config.mjs` at the repo root, derived from `package/eslint.config.mjs` (full feature set: TS, React, React-Native, import order, Prettier, Jest, Markdown) but with **`@react-native-community/eslint-*` replaced by `@react-native/eslint-config` + `@react-native/eslint-plugin`** and the `globals` parsing logic adapted (the modern config uses `true` instead of `'readonly'` — see `examples/SampleApp/eslint.config.mjs:17-22` for the existing pattern).
28+
- The shared config applies to all source files; **file-glob overrides** keep small workspace-specific behaviors (Jest test files, example apps' lower bar where unavoidable — e.g. ignoring generated files under `examples/*/ios/build/`, Metro/Babel config files).
29+
- Move every eslint/prettier-related devDependency to **root `devDependencies`**. Delete them from sub-workspace `package.json`s. Yarn 4 with `nmHoistingLimits: workspaces` puts root devDeps in `<repo>/node_modules/`, so Node resolution walks up and finds them from any sub-workspace.
30+
- Delete `package/eslint.config.mjs`, `examples/SampleApp/eslint.config.mjs`, `examples/TypeScriptMessaging/eslint.config.mjs`. ESLint flat-config auto-discovery walks up from the cwd until it finds an `eslint.config.mjs`, so a single config at the root covers every workspace.
31+
- Redefine the **root `lint` / `lint-fix` / `eslint` scripts** to do the work in place (instead of proxying to a workspace). New shape:
32+
- `lint`: `prettier --list-different . && eslint . --max-warnings 0 && yarn workspace stream-chat-react-native-core validate-translations`
33+
- `lint-fix`: `prettier --write . && eslint . --fix --max-warnings 0`
34+
- `eslint`: `eslint .`
35+
- `prettier`: `prettier --list-different .`
36+
- `prettier-fix`: `prettier --write .`
37+
- The core package keeps its **`validate-translations`** script (it depends on translation files that only exist under `package/src/i18n/`); the new root `lint` script calls into it via `yarn workspace`.
38+
- Sub-workspace `lint`/`eslint`/`lint-fix`/`prettier*` scripts are **deleted** (with one exception: `validate-translations` stays in core).
39+
- The pre-commit hook and the CI workflows already call `yarn lint` from the repo root — no changes needed there.
40+
- `.prettierignore` and `.prettierrc` stay where they are at the root.
41+
- Extend `.prettierignore` to also exclude example app build artifacts that aren't currently listed but will start being scanned once Prettier runs from the root (`examples/*/ios/Pods/`, `examples/*/android/build/`, `examples/*/.expo/`, `examples/SampleApp/patches/` if relevant, etc.). Verify by running `prettier --list-different .` after the move and adding any noisy paths to the ignore file rather than fixing them.
42+
43+
## Files to change
44+
45+
**Add**
46+
47+
- `eslint.config.mjs` — new root flat config (copy of `package/eslint.config.mjs` with modern `@react-native/*` imports + adapted globals parsing + broader `ignores` covering all workspaces' build outputs + example-app overrides if needed).
48+
49+
**Modify**
50+
51+
- `package.json` (root): add eslint/prettier-related devDeps; rewrite `eslint` / `lint` / `lint-fix` scripts; add `prettier` / `prettier-fix` scripts; bump `prettier` to `^3.5.3` to match what core/examples already pin.
52+
- `.prettierignore`: extend with any example-app paths that surface noise once Prettier scans the whole repo.
53+
- `package/package.json`: remove every eslint/prettier-related entry from `devDependencies` (`eslint`, `typescript-eslint`, `eslint-config-prettier`, `eslint-plugin-prettier`, `eslint-plugin-eslint-comments`, `eslint-plugin-import`, `eslint-plugin-jest`, `eslint-plugin-markdown`, `eslint-plugin-react`, `eslint-plugin-react-hooks`, `eslint-plugin-react-native`, `@react-native-community/eslint-config`, `@react-native-community/eslint-plugin`, `prettier`); remove `eslint` / `lint` / `lint-fix` / `prettier` / `prettier-fix` scripts; **keep `validate-translations`**.
54+
- `examples/SampleApp/package.json`: remove eslint/prettier devDeps; remove `lint` / `eslint` / `lint-fix` scripts.
55+
- `examples/TypeScriptMessaging/package.json`: remove eslint/prettier devDeps; remove `lint` script.
56+
57+
**Delete**
58+
59+
- `package/eslint.config.mjs`
60+
- `examples/SampleApp/eslint.config.mjs`
61+
- `examples/TypeScriptMessaging/eslint.config.mjs`
62+
63+
**Unchanged (no edits needed, but verify nothing breaks)**
64+
65+
- `.prettierrc` (already root-owned)
66+
- `.husky/pre-commit` and `dotgit/hooks/pre-commit-format.sh` (call `yarn lint`, which still resolves to the redefined root script)
67+
- `.github/workflows/check-pr.yml`, `.github/workflows/release.yml` (call `yarn lint`)
68+
- `.vscode/settings.json` (only sets `formatOnSave`, picks up root `.prettierrc` automatically)
69+
- `package/.editorconfig`
70+
71+
## Step-by-step migration
72+
73+
1. **Snapshot baseline** — run `yarn lint` on `develop` and save the pass/fail output, so any new errors after the migration are clearly attributable to the new ruleset.
74+
2. **Add root `eslint.config.mjs`** derived from core's. Concretely:
75+
- Replace `@react-native-community/eslint-config``@react-native/eslint-config`, `@react-native-community/eslint-plugin``@react-native/eslint-plugin`, and rename the corresponding `plugins` key from `'@react-native-community'` to `'@react-native'`.
76+
- Adapt the globals reducer (the modern config stores `true`/`false`, not `'readonly'`) — pattern shown in `examples/SampleApp/eslint.config.mjs:17-22`.
77+
- Broaden the top-level `ignores` to cover every workspace: `node_modules/`, `**/build/`, `**/dist/`, `**/lib/`, `**/.expo/`, `**/vendor/`, `**/ios/build/`, `**/ios/Pods/`, `**/android/build/`, `**/android/app/build/`, `package/src/components/docs/`, plus any Metro-generated dirs in examples.
78+
- Keep the Jest overlay for `**/__tests__/**`, `**/*.test.*`, and `src/mock-builders/**` (the glob already covers all workspaces; verify with a dry run).
79+
- If example apps trip new rules that aren't worth fixing immediately, add a third overlay `{ files: ['examples/**/*.{js,ts,tsx,jsx}'], rules: { ... } }` with targeted relaxations. Prefer fixing real issues over piling up disables.
80+
3. **Update root `package.json`**:
81+
- Add devDeps (versions match what's installed today in core/examples to minimize lockfile churn): `eslint ^9.28.0`, `typescript-eslint ^8.34.0`, `eslint-config-prettier ^10.1.5`, `eslint-plugin-prettier ^5.4.1`, `eslint-plugin-eslint-comments ^3.2.0`, `eslint-plugin-import ^2.31.0`, `eslint-plugin-jest ^28.13.3`, `eslint-plugin-markdown ^5.1.0`, `eslint-plugin-react ^7.37.5`, `eslint-plugin-react-hooks ^5.2.0`, `eslint-plugin-react-native ^5.0.0`, `@react-native/eslint-config ^0.81.6`, `@react-native/eslint-plugin ^0.81.6`. Bump root `prettier` from `^3.5.1` to `^3.5.3`.
82+
- Rewrite scripts as described in the Approach section.
83+
4. **Strip sub-workspace `package.json`s** of eslint/prettier devDeps and lint scripts. Delete their `eslint.config.mjs` files.
84+
5. **Run `yarn install`** to regenerate `yarn.lock`. Confirm only intended deps moved/were removed.
85+
6. **Run `yarn lint`** from the repo root. Triage:
86+
- True regressions → fix in code.
87+
- Stylistic differences from the modern `@react-native/eslint-config` vs the legacy community plugin → reconcile by adjusting the root config rules (favour preserving today's core behavior where it conflicts with the modern defaults).
88+
- Example-app noise from now-stricter linting → fix or relax via the `examples/**` overlay.
89+
7. **Run `yarn lint-fix`** and re-run `yarn lint`. Expect a clean pass.
90+
8. **Run the pre-commit hook locally** (`./dotgit/hooks/pre-commit-format.sh` after staging a small no-op change) to confirm it still works.
91+
9. **Spot-check editor integration**: open a file in `examples/SampleApp/` in VSCode, save, and confirm Prettier formats it and ESLint diagnostics show up.
92+
10. **Push & verify CI**`check-pr.yml`'s `yarn lint` step is the canonical signal.
93+
94+
## Verification
95+
96+
- `yarn install --immutable` succeeds after the lockfile regeneration commit is in place.
97+
- `yarn lint` from the repo root exits 0 and reports zero warnings (`--max-warnings 0`).
98+
- `yarn lint` from any sub-workspace directory (`cd examples/SampleApp && yarn lint`) either runs the root script (if we keep a thin proxy) or fails with a clear "no script" message — confirm the chosen behavior is documented.
99+
- `yarn lint-fix` rewrites only intended files; `git status` after a clean checkout + `yarn lint-fix` shows no diff.
100+
- `node -e "require('eslint/package.json').version"` from `examples/SampleApp/` returns the root-installed version (proves hoisting works).
101+
- Pre-commit hook: introduce a deliberate formatting violation in a staged file, attempt `git commit`, confirm it is rejected with the existing message.
102+
- CI: `check-pr.yml` and `release.yml` both green on a draft PR.
103+
- Grep sanity check: `grep -R "eslint-plugin\|@react-native-community/eslint\|@react-native/eslint" package examples` returns no matches in any `package.json` other than the root.
104+
105+
## Risks & mitigations
106+
107+
- **Rule-set drift between legacy and modern RN configs.** The modern `@react-native/eslint-config` ships a different default rule set than `@react-native-community/eslint-config@3.2.0`. Mitigation: derive the root config from the current core config and override modern defaults to match today's behavior wherever they conflict; treat any net-new errors as either a real bug or a targeted disable.
108+
- **Yarn hoisting edge cases.** With `nmHoistingLimits: workspaces`, root devDeps land in `<repo>/node_modules/` and resolve via Node walking up. If a sub-workspace ships its own copy of a peer-dependency that conflicts, ESLint plugin resolution can pick the wrong one. Mitigation: after `yarn install`, run `yarn why eslint` and `yarn why @react-native/eslint-config` and confirm a single instance is hoisted to the root.
109+
- **Different RN versions in examples.** `examples/SampleApp` is on RN 0.81.x; `examples/TypeScriptMessaging` is on RN 0.80.x. We're pinning one `@react-native/eslint-config` version. Lint rules are largely version-agnostic; the worst case is a couple of cosmetic differences that get resolved by `yarn lint-fix`.
110+
- **Markdown linting.** The existing core config keeps `eslint-plugin-markdown` in deps but the active config doesn't actually wire it up as a plugin (the file extension is in the `eslint` glob, not the flat config plugins). Verify whether dropping `eslint-plugin-markdown` entirely is safe; if so, omit it from the root devDeps. (Decision deferred to execution — check by removing the dep, running lint, and seeing if anything regresses.)
111+
- **`enableHardenedMode: true` in `.yarnrc.yml`** means dependency additions are scrutinized. None of the deps we're moving are new to the repo (they all already exist in sub-workspaces), so the audit surface doesn't grow. Lockfile regen should be uneventful.
112+
113+
## Out of scope
114+
115+
- Modernizing the rule set (e.g. enabling new TypeScript strictness rules, tightening `react-hooks/exhaustive-deps` from `warn` to `error`). The migration aims for behavioral parity; rule changes are a follow-up.
116+
- Switching to a separate ESLint config package (e.g. `tooling/eslint-config-stream-chat-rn`). The root-config approach is simpler and matches the "root owns the config" intent. If we ever publish other RN SDKs from this repo, that can be revisited.
117+
- Touching test runners, TypeScript configs, or Husky setup.

.prettierignore

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,45 @@
1+
# Dependencies / build artifacts
12
**/node_modules/
23
**/build/
34
**/dist/
5+
**/lib/
46
**/.expo/
7+
**/.gradle/
58
**/vendor/
9+
**/ios/build/
10+
**/ios/Pods/
11+
**/android/build/
12+
**/android/app/build/
13+
**/coverage/
14+
15+
# Native scaffolding (not in this project's purview)
16+
**/ios/
17+
**/android/
18+
**/fastlane/
19+
examples/SampleApp/patches/
20+
21+
# Lockfiles & generated metadata
22+
**/*.lock
23+
**/Podfile.lock
24+
.yarn/
25+
yarn.lock
26+
27+
# Generated SDK assets
628
package/src/components/docs/
7-
package/lib/
29+
package/src/theme/generated/
30+
31+
# Workflow / tooling configs not owned by this project's Prettier
32+
.github/
33+
.husky/
34+
dotgit/
35+
36+
# Repo metadata & docs that don't follow Prettier rules
37+
.claude/
38+
ai-docs/
39+
AGENTS.md
40+
CLAUDE.md
41+
PULL_REQUEST_TEMPLATE.md
42+
RELEASE_PROCESS.md
43+
SECURITY.md
44+
**/CHANGELOG.md
45+
**/.watchmanconfig
Lines changed: 74 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import jsEslint from '@eslint/js';
2-
import eslintReactNativeConfig from '@react-native-community/eslint-config';
3-
import eslintPluginReactNativeOfficial from '@react-native-community/eslint-plugin';
2+
import eslintReactNativeConfig from '@react-native/eslint-config';
3+
import eslintPluginReactNativeOfficial from '@react-native/eslint-plugin';
44
import eslintPluginComments from 'eslint-plugin-eslint-comments';
55
import eslintPluginImport from 'eslint-plugin-import';
66
import eslintPluginJest from 'eslint-plugin-jest';
@@ -13,39 +13,77 @@ import tsEslint from 'typescript-eslint';
1313
import eslintConfigPrettier from 'eslint-config-prettier';
1414

1515
/**
16-
* @react-native-community/eslint-config is for some reason still using the old notation
17-
* for globals. We parse them manually here to make sure they're compatible with
18-
* the new config. All globals are now readonly to prevent them from causing trouble.
16+
* @react-native/eslint-config stores globals as `true`/`false`. We rebuild the
17+
* map with `'readonly'` so flat config accepts them.
1918
*/
20-
const reactNativeGlobals = Object.keys(eslintReactNativeConfig.globals).reduce((acc, key) => {
21-
acc[key] = 'readonly';
22-
return acc;
23-
}, {});
24-
25-
/**
26-
* We filter out the jest/ rules as they're part of another config layer.
27-
*/
28-
const reactNativeRules = Object.keys(eslintReactNativeConfig.rules).reduce((acc, key) => {
29-
if (!key.startsWith('jest/')) {
30-
acc[key] = eslintReactNativeConfig.rules[key];
19+
const reactNativeGlobals = Object.keys(eslintReactNativeConfig.globals ?? {}).reduce((acc, key) => {
20+
if (eslintReactNativeConfig.globals[key]) {
21+
acc[key] = 'readonly';
3122
}
3223
return acc;
3324
}, {});
3425

26+
const reactNativeRules = Object.fromEntries(
27+
Object.entries(eslintReactNativeConfig.rules ?? {}).filter(([key]) => !key.startsWith('jest/')),
28+
);
29+
3530
export default tsEslint.config(
3631
jsEslint.configs.recommended,
3732
tsEslint.configs.recommended,
3833
eslintPluginReact.configs.flat.recommended,
3934
{
4035
ignores: [
41-
'node_modules/',
42-
'build/',
43-
'dist/',
44-
'.expo/',
45-
'vendor/',
46-
'*.md',
47-
'src/components/docs/',
48-
'lib/',
36+
// Dependencies and build outputs
37+
'**/node_modules/',
38+
'**/build/',
39+
'**/dist/',
40+
'**/lib/',
41+
'**/.expo/',
42+
'**/.gradle/',
43+
'**/vendor/',
44+
'**/coverage/',
45+
'**/ios/build/',
46+
'**/ios/Pods/',
47+
'**/android/build/',
48+
'**/android/app/build/',
49+
50+
// Native scaffolding (not source we lint)
51+
'**/ios/',
52+
'**/android/',
53+
'**/fastlane/',
54+
55+
// Generated SDK assets
56+
'package/src/components/docs/',
57+
'package/src/theme/generated/',
58+
59+
// Jest snapshots (auto-generated)
60+
'**/__snapshots__/',
61+
'**/*.snap',
62+
63+
// JSON is formatted by Prettier, not linted by ESLint
64+
'**/*.json',
65+
66+
// Markdown is not linted (core has historically excluded it)
67+
'**/*.md',
68+
69+
// Tooling config files
70+
'**/*.config.js',
71+
'**/*.config.cjs',
72+
'**/*.config.mjs',
73+
'**/jest-setup.*',
74+
75+
// Workspaces / dirs not previously in lint scope
76+
'package/native-package/',
77+
'package/expo-package/',
78+
'release/',
79+
80+
// Repo metadata
81+
'.github/',
82+
'.husky/',
83+
'dotgit/',
84+
'.claude/',
85+
'ai-docs/',
86+
'docs/',
4987
],
5088
},
5189
{
@@ -64,7 +102,7 @@ export default tsEslint.config(
64102
sourceType: 'module',
65103
},
66104
plugins: {
67-
'@react-native-community': eslintPluginReactNativeOfficial,
105+
'@react-native': eslintPluginReactNativeOfficial,
68106
'eslint-comments': eslintPluginComments,
69107
import: eslintPluginImport,
70108
prettier: eslintPluginPrettier,
@@ -107,7 +145,10 @@ export default tsEslint.config(
107145
'comma-dangle': 0,
108146
'default-case': 2,
109147
eqeqeq: [2, 'smart'],
110-
'import/no-unresolved': ['error', { ignore: ['types'] }],
148+
// TypeScript already catches unresolved imports; the eslint-import-resolver-node
149+
// resolver doesn't understand Yarn workspace hoisting or TS path aliases, so we
150+
// turn this off repo-wide to avoid false positives in example apps.
151+
'import/no-unresolved': 'off',
111152
'import/order': [
112153
'error',
113154
{
@@ -147,7 +188,12 @@ export default tsEslint.config(
147188
'@typescript-eslint/ban-ts-comment': 0,
148189
'@typescript-eslint/no-unused-vars': [
149190
'warn',
150-
{ ignoreRestSiblings: false, caughtErrors: 'none' },
191+
{
192+
ignoreRestSiblings: false,
193+
caughtErrors: 'none',
194+
argsIgnorePattern: '^_',
195+
varsIgnorePattern: '^_',
196+
},
151197
],
152198
'@typescript-eslint/no-unused-expressions': 'off',
153199
'@typescript-eslint/no-var-requires': 0,
@@ -161,7 +207,7 @@ export default tsEslint.config(
161207
},
162208
{
163209
name: 'jest',
164-
files: ['src/**/__tests__/**', '**/*.test.*', 'src/mock-builders/**'],
210+
files: ['**/__tests__/**', '**/*.test.*', 'package/src/mock-builders/**'],
165211
plugins: { jest: eslintPluginJest },
166212
languageOptions: {
167213
globals: {

examples/ExpoMessaging/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ git clone https://github.com/GetStream/stream-chat-react-native.git
1313
```
1414

1515
### Install dependencies
16-
16+
1717
1. In the root install the dependencies:
1818

1919
```bash

0 commit comments

Comments
 (0)