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
24 changes: 24 additions & 0 deletions .github/workflows/CI_lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Lint

on:
pull_request:

jobs:
lint:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint
6 changes: 6 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint"
]
}
11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.useFlatConfig": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
]
}
58 changes: 58 additions & 0 deletions LINTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Linting

OcotilloUI uses [ESLint](https://eslint.org/) with the flat config format (`eslint.config.ts`) and [Prettier](https://prettier.io/) for formatting.

## Running locally

```bash
# Check for lint errors and warnings
npm run lint

# Auto-fix fixable issues
npm run lint:fix

# Check formatting only (Prettier)
npx prettier --check .

# Auto-format all files
npx prettier --write .
```

## Ruleset

The config lives in `eslint.config.ts` at the repo root. It applies to all `*.ts` and `*.tsx` files and covers:

| Plugin | Purpose |
|--------|---------|
| `@eslint/js` | Core JS recommended rules |
| `typescript-eslint` | TypeScript type-aware rules |
| `eslint-plugin-react` | React best practices |
| `eslint-plugin-react-hooks` | Enforces the rules of hooks |
| `eslint-plugin-react-refresh` | Vite HMR compatibility |
| `eslint-config-prettier` | Disables rules that conflict with Prettier |

### Key rules

- `@typescript-eslint/no-explicit-any` — **warn**. Explicit `any` is a signal to improve typing. Not an error yet to allow gradual cleanup.
- `@typescript-eslint/no-unused-vars` — **warn**. Variables prefixed with `_` are exempt.
- `react-hooks/rules-of-hooks` — **error**. Hooks called outside components or conditionally break React.
- `react-hooks/exhaustive-deps` — **warn**. Missing `useEffect` deps are a common source of stale closure bugs.

## CI enforcement

`CI_vitest.yml` runs `npm run lint` on every pull request. The build fails on any ESLint **error**. Warnings are reported but do not block merge.

The goal is to graduate all current warnings to errors once the codebase is clean. See [Epic 1, Ticket 1.2](../nm-water-data/tickets/epics/epic-01-code-health.md) for the full plan.

## Prettier config

Prettier is configured in `.prettierrc`:

```json
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true
}
```
93 changes: 93 additions & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import js from '@eslint/js'
import globals from 'globals'
import tseslint from 'typescript-eslint'
import reactPlugin from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import prettierConfig from 'eslint-config-prettier'

export default tseslint.config(
// Files and directories to ignore entirely
{
ignores: [
'dist/**',
'node_modules/**',
'src/generated/**',
'coverage/**',
'cypress/**',
],
},

// Base JS recommended rules
js.configs.recommended,

// React flat/recommended (ESLint 10 compatible)
// @ts-expect-error — flat config types not fully typed in this plugin version
reactPlugin.configs.flat.recommended,
// @ts-expect-error — flat config types not fully typed in this plugin version
reactPlugin.configs.flat['jsx-runtime'],

// TypeScript + React-specific rules
{
files: ['**/*.{ts,tsx}'],
extends: [...tseslint.configs.recommended],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaFeatures: { jsx: true },
},
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
// Hooks
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],

// TypeScript covers prop validation — disable the React-only version
'react/prop-types': 'off',
'react/display-name': 'off',

// React Compiler rules — demote from error to warn until violations are
// fixed and the compiler is actually enabled.
// See docs/product/decisions/react-compiler.md in the-brain repo.
'react-hooks/static-components': 'warn',
'react-hooks/use-memo': 'warn',
'react-hooks/preserve-manual-memoization': 'warn',
'react-hooks/immutability': 'warn',
'react-hooks/globals': 'warn',
'react-hooks/refs': 'warn',
'react-hooks/set-state-in-effect': 'warn',
'react-hooks/error-boundaries': 'warn',
'react-hooks/purity': 'warn',
'react-hooks/set-state-in-render': 'warn',
'react-hooks/config': 'warn',
'react-hooks/gating': 'warn',

// TypeScript — warn rather than error for a realistic first-run baseline
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'@typescript-eslint/no-empty-object-type': 'warn',
'@typescript-eslint/no-require-imports': 'warn',
},
settings: {
react: { version: 'detect' },
},
},

// Disable all rules that conflict with Prettier formatting
prettierConfig,
)
Loading
Loading