Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .changeset/add-eslint-config-expo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
'@lyracom/eslint-config-expo': major
---

Add `@lyracom/eslint-config-expo`, Lyra's ESLint config for Expo React Native apps.

Flat config (ESLint 9+) built on top of `eslint-config-expo` (React, React Hooks,
import resolution and TypeScript baseline tuned for Expo / React Native), with
`eslint-config-prettier` applied last so Prettier stays the single source of
truth for formatting.

The package exports two named flat-config arrays: `lyraExpoConfig` (the main
config) and `reactCompilerRecommendedWarn` (optional, see below).

On top of the Expo baseline it adds a lean house-rules layer (all `warn` during
migration unless noted otherwise):

- The `@typescript-eslint` **recommended** preset, layered before Expo (so Expo's
RN-specific tuning still wins) and downgraded to `warn`.
- Type-aware `@typescript-eslint/no-deprecated` (requiring `typescript` + a
tsconfig), replacing the archived `eslint-plugin-deprecation`.
- `@typescript-eslint/no-floating-promises` and `@typescript-eslint/no-misused-promises`
are explicitly disabled: runtime monitoring handles unhandled failures, and
intentional fire-and-forget calls use `void` where appropriate.
- `@typescript-eslint/no-empty-function` (allowing empty arrow functions, since
they're an idiomatic no-op default in React Contexts).
- `prefer-promise-reject-errors` and `react/display-name` left off (noisy in RN).
- `react/self-closing-comp` enabled as an **error** for consistent JSX (`<View />`
instead of `<View></View>`).
- Type-aware `eslint-plugin-import` rules disabled where redundant with
TypeScript and expensive to run: `import/namespace`,
`import/no-named-as-default-member`, `import/no-unresolved`.
- Bug rules ESLint 10 promotes to recommended, enabled early on v9:
`no-unassigned-vars`, `no-useless-assignment`, `preserve-caught-error`.
- React Compiler-oriented Hooks rules are enabled by the Expo preset. Projects
migrating gradually can use the opt-in
`reactCompilerRecommendedWarn` named preset to downgrade them all to warnings
with one config spread.
5 changes: 5 additions & 0 deletions .changeset/bump-prettier-import-plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lyracom/prettier-config': major
---

Require `@trivago/prettier-plugin-sort-imports` v6 as a peer dependency.
122 changes: 103 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
# Coding style for Lyra JavaScript apps
# Coding style for Lyra apps

A monorepo containing coding style related packages for Lyra JavaScript apps.
This coding style includes linting and formatting rules.
A monorepo containing coding style related packages for Lyra apps (primarily Expo React Native). This coding style
includes linting and formatting rules.

- [Prerequisites](#prerequisites)
- [Technical stack](#technical-stack)
- [Packages](#packages)
- [Usage](#usage)
- [Installation](#installation)
- [Configuration](#configuration)
- [IDE integration](#ide-integration)
- [Contribution](#contribution)

## Technical stack

To use this coding style, we assume that your technical stack contains:

- [expo](https://expo.dev/) / [react-native](https://reactnative.dev/)
- [typescript](https://www.typescriptlang.org/)
- [eslint](https://eslint.org/) for linting
- [eslint](https://eslint.org/) for linting — ESLint 9 flat config for Expo apps (`@lyracom/eslint-config-expo`), legacy
ESLint 8 `.eslintrc` configs for other project types (see [ESLint](#eslint) below)
- [prettier](https://prettier.io/) for formatting
- [jest](https://jestjs.io/) for testing

Expand All @@ -23,6 +26,7 @@ To use this coding style, we assume that your technical stack contains:
| Package | Description | Version |
| --------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| [`@lyracom/eslint-config`](/packages/eslint-config) | ESLint config for pure JavaScript or TypeScript projects | [![npm (scoped)](https://img.shields.io/npm/v/@lyracom/eslint-config)](https://www.npmjs.com/package/@lyracom/eslint-config) |
| [`@lyracom/eslint-config-expo`](/packages/eslint-config-expo) | ESLint config for Expo React Native apps (flat config) | [![npm (scoped)](https://img.shields.io/npm/v/@lyracom/eslint-config-expo)](https://www.npmjs.com/package/@lyracom/eslint-config-expo) |
| [`@lyracom/eslint-config-node`](/packages/eslint-config-node) | ESLint config for Node.js applications | [![npm (scoped)](https://img.shields.io/npm/v/@lyracom/eslint-config-node)](https://www.npmjs.com/package/@lyracom/eslint-config-node) |
| [`@lyracom/eslint-config-react`](/packages/eslint-config-react) | ESLint config for React applications | [![npm (scoped)](https://img.shields.io/npm/v/@lyracom/eslint-config-react)](https://www.npmjs.com/package/@lyracom/eslint-config-react) |
| [`@lyracom/prettier-config`](/packages/prettier-config) | Prettier config for TypeScript projects | [![npm (scoped)](https://img.shields.io/npm/v/@lyracom/prettier-config)](https://www.npmjs.com/package/@lyracom/prettier-config) |
Expand All @@ -33,17 +37,17 @@ To use this coding style, we assume that your technical stack contains:

```sh
pnpm add -D \
"typescript@^5.6" \
"typescript@~5.5.4" \
"@lyracom/tsconfig@^1"
```

Look at all the configurations available in the package (base/node/dom/jsx) and chose the one you want to use in your project. Then create a tsconfig.json file and add the following lines:
Look at all the configurations available in the package (base/node/dom/jsx) and chose the one you want to use in your
project. Then create a tsconfig.json file and add the following lines:

```json
{
// always try to use strict TypeScript config
"extends": ["@lyracom/tsconfig/tsconfig-strict.json", "@lyracom/tsconfig/tsconfig-[CONFIG].json"],

// or, if you work on a legacy/poc project or do not want to use strict config
"extends": "@lyracom/tsconfig/tsconfig-[CONFIG].json"
}
Expand All @@ -55,15 +59,76 @@ Look at all the configurations available in the package (base/node/dom/jsx) and
pnpm add -D \
"prettier@^3" \
"@lyracom/prettier-config@^6" \
"@trivago/prettier-plugin-sort-imports@^4"
"@trivago/prettier-plugin-sort-imports@^6"
```

The prettier config uses the package `@trivago/prettier-plugin-sort-imports` to sort imports. You can customize this config if you need a specific order in your imports, if you have import aliases for example.
The prettier config uses the package `@trivago/prettier-plugin-sort-imports` to sort imports. You can customize this
config if you need a specific order in your imports, if you have import aliases for example.

Check available options on their website: https://github.com/trivago/prettier-plugin-sort-imports

### ESLint

<details open>
<summary><b>For Expo React Native apps (flat config, ESLint 9+) — recommended</b></summary>

```sh
pnpm add -D \
"eslint@^9" \
"typescript@^5" \
"@lyracom/eslint-config-expo"
```

Create an `eslint.config.js` at the project root:

```js
const { lyraExpoConfig } = require('@lyracom/eslint-config-expo')

module.exports = [
...lyraExpoConfig,
{
// anchor type-aware linting to this project
languageOptions: { parserOptions: { tsconfigRootDir: __dirname } },
},
{
ignores: ['dist/*', '.expo/*'],
},
]
```

This config enables the type-aware `@typescript-eslint/no-deprecated` rule, so every `.ts`/`.tsx` file must be covered
by your `tsconfig.json`. Promise safety rules (`no-floating-promises` and `no-misused-promises`) are explicitly
disabled:
runtime monitoring handles unhandled failures, and intentional fire-and-forget calls use `void` where appropriate. If
ESLint reports _"file was not found by the project service"_, add the file to your tsconfig `include` (or ignore it in
`eslint.config.js`).

The React Compiler-oriented Hooks rules are enabled by the Expo preset as errors. During migration, projects can
downgrade them all to warnings with one additional config spread:

```js
const { lyraExpoConfig, reactCompilerRecommendedWarn } = require('@lyracom/eslint-config-expo')

module.exports = [...lyraExpoConfig, ...reactCompilerRecommendedWarn]
```

**Migrating from `@lyracom/eslint-config-react` (legacy `.eslintrc`):**

- Convert your `.eslintrc.js` to `eslint.config.js` (flat config).
- **Delete** any `promise/*` and `sonarjs/*` overrides — those plugins are no longer bundled, and referencing an
unregistered rule is a hard error in flat config.
- **Delete** the `eslint-plugin-deprecation` plugin + `deprecation/deprecation`
override; it is replaced by the shared, type-aware `@typescript-eslint/no-deprecated`.
- Drop overrides now handled by the shared config: `@typescript-eslint/no-empty-function`,
`@typescript-eslint/ban-ts-comment`, `react/display-name`, `react/prop-types`.
- Keep only genuinely app-specific rules (e.g. `react-hooks/exhaustive-deps`
`additionalHooks`).

</details>

<details>
<summary><b>Legacy configs (deprecated, ESLint 8 / <code>.eslintrc</code>)</b></summary>

```sh
pnpm add -D \
"eslint@^8" \
Expand Down Expand Up @@ -109,6 +174,8 @@ pnpm add -D \

</details>

</details>

### Husky

Now install `husky`:
Expand All @@ -125,7 +192,25 @@ pnpm add -D lint-staged

## Configuration

In your `package.json`, add the following lines:
For an Expo app, create the `eslint.config.js` shown above, then add the shared Prettier config and a `lint-staged`setup
to your `package.json`:

```json
{
"prettier": "@lyracom/prettier-config",
"lint-staged": {
"*.{js,jsx,ts,tsx}": "eslint",
"*": "prettier -w -u"
}
}
```

> The shared ESLint config reports most rules as `warn` during migration, so
> keep `eslint` (not `eslint --max-warnings 0`) in `lint-staged` for now. Switch
> to `eslint --max-warnings 0` once your app is clean to make warnings blocking.

<details>
<summary><b>Legacy configs (<code>.eslintrc</code>)</b></summary>

```json
{
Expand All @@ -136,15 +221,12 @@ In your `package.json`, add the following lines:
"extends": "@lyracom/eslint-config-node",
// For React projects
"extends": "@lyracom/eslint-config-react"
},
"prettier": "@lyracom/prettier-config",
"lint-staged": {
"*.{js,jsx,ts,tsx}": "eslint --max-warnings 0",
"*": "prettier -w -u"
}
}
```

</details>

In `.husky/pre-commit`, add the following lines:

```sh
Expand All @@ -164,7 +246,8 @@ Optionally, here are some `package.json` scripts you can inspire from:
}
```

Do not forget to use `.prettierignore` and `.eslintignore` files to exclude folders and files you don't want to format/lint.
Do not forget to use `.prettierignore` to exclude files you don't want to format, and the `ignores` key in
`eslint.config.js` to exclude files from linting.

## IDE integration

Expand All @@ -184,7 +267,8 @@ Do not push directly on main but submit a pull request instead, thanks :)

### Changesets

All changes are managed with [changesets](https://github.com/changesets/changesets). Whenever you commit a change who deserves to appear in the changelog, you need to include a change using the command: `pnpm changeset`.
All changes are managed with [changesets](https://github.com/changesets/changesets). Whenever you commit a change who
deserves to appear in the changelog, you need to include a change using the command: `pnpm changeset`.

### Continuous integration

Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
},
"devDependencies": {
"@changesets/cli": "^2.26.1",
"@types/jest": "^29",
"enhanced-resolve": "^5.12.0",
"husky": "^8.0.3",
"jest": "^29.5.0",
"lint-staged": "^13.2.1",
"prettier": "^3.3.3",
"husky": "^9.0.11",
"jest": "~29.7.0",
"lint-staged": "^17.3.0",
"prettier": "^3.9.6",
"typescript": "^5.6.3"
},
"prettier": "./packages/prettier-config/index.json",
Expand Down
21 changes: 21 additions & 0 deletions packages/eslint-config-expo/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2022 Lyra

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
15 changes: 15 additions & 0 deletions packages/eslint-config-expo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# `@lyracom/eslint-config-expo`

> Lyra's ESLint config for Expo React Native apps.

[![npm (scoped)](https://img.shields.io/npm/v/@lyracom/eslint-config-expo)](https://www.npmjs.com/package/@lyracom/eslint-config-expo)

This package is part of « [Coding style for Lyra apps](https://github.com/lyra/coding-style) », please follow instructions from this repo.

The package exports named flat-config arrays:

```js
const { lyraExpoConfig, reactCompilerRecommendedWarn } = require('@lyracom/eslint-config-expo')

module.exports = [...lyraExpoConfig, ...reactCompilerRecommendedWarn]
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @ts-expect-error no react dep
import * as React from 'react'

interface Props {
name: string
age: number
}

const Foo: React.FC<Props> = ({ name, age }) => {
// this should be a warning (@typescript-eslint/no-unused-vars)
const foo = `Hello ${name}`

// this should be an error (@typescript-eslint/prefer-as-const)
const bar: 2 = 2

return (
<div>
<h1>My name is {name}.</h1>
<h2>I am {age}.</h2>
</div>
)
}

export default Foo
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// @ts-expect-error no react dep
import * as React from 'react'

interface Props {
name: string
age: number
}

const Foo: React.FC<Props> = ({ name, age }) => {
return (
<div>
<h1>My name is {name}.</h1>
<h2>I am {age}.</h2>
</div>
)
}

export default Foo
11 changes: 11 additions & 0 deletions packages/eslint-config-expo/__tests__/fixtures/file-with-errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// error expected (import/no-unresolved)
import notFound from './nope'

export function foo() {
const bar = 2

// error expected (no-const-assign, no-unused-vars)
bar = 42

return 'foo'
}
14 changes: 14 additions & 0 deletions packages/eslint-config-expo/__tests__/fixtures/file-with-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// error expected (import/no-unresolved)
import notFound from './nope'

export function foo() {
// warning expected (@typescript-eslint/no-unused-vars)
const unused = notFound

const value = 2

// error expected (no-const-assign)
value = 42

return value
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Exercises rules from the @typescript-eslint "recommended" preset (as warnings).

// warning expected (@typescript-eslint/no-explicit-any)
export function identity(value: any): unknown {
return value
}

export function two(): number {
// warning expected (@typescript-eslint/prefer-as-const)
const value: 2 = 2

return value
}
Loading