-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy patheslint.config.mjs
More file actions
238 lines (216 loc) · 8.17 KB
/
Copy patheslint.config.mjs
File metadata and controls
238 lines (216 loc) · 8.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginReact from "eslint-plugin-react";
import pluginReactHooks from "eslint-plugin-react-hooks";
import pluginNext from "@next/eslint-plugin-next";
import pluginJest from "eslint-plugin-jest";
import pluginUnusedImports from "eslint-plugin-unused-imports";
/** @type {import('eslint').Linter.Config[]} */
export default [
// Ignore patterns
{
ignores: [
".next/**",
"node_modules/**",
"dist/**",
"build/**",
"out/**",
"coverage/**",
"public/static/**",
"update-postman-collection.ts",
],
},
// Jest test files configuration
{
files: ["**/*.{test,spec}.{js,ts,jsx,tsx}", "jest.setup.ts"],
languageOptions: {
globals: {
...globals.jest,
},
},
plugins: {
jest: pluginJest,
},
rules: {
...pluginJest.configs.recommended.rules,
},
},
// Base config for all JavaScript/TypeScript files. `mts`/`cts` included so
// netlify/functions/*.mts (#1356 — the scheduled ticker) is linted rather
// than silently skipped; it was previously the only extension this repo
// ships that fell through every `files` glob below.
{
files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
plugins: {
"unused-imports": pluginUnusedImports,
},
},
// Core JavaScript rules
pluginJs.configs.recommended,
// TypeScript rules
...tseslint.configs.recommended,
// React specific configuration
{
...pluginReact.configs.flat.recommended,
settings: {
react: {
version: "detect",
},
},
plugins: {
...pluginReact.configs.flat.recommended.plugins,
"react-hooks": pluginReactHooks,
"@next/next": pluginNext,
},
rules: {
// Warns when let is used where const could be used instead
"prefer-const": "warn",
// Warns when var is used instead of let or const
"no-var": "warn",
// Warn about lexical declarations in case blocks without braces
"no-case-declarations": "warn",
// Enforce === and !== over == and !=
eqeqeq: "warn",
// React hooks rules
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
// Disabled — TypeScript handles prop validation
"react/prop-types": "off",
// Disabled — React 17+ JSX transform doesn't require importing React
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
// Next.js specific rules
...pluginNext.configs.recommended.rules,
// Warn when empty object types are used (e.g. 'type Foo = {}')
"@typescript-eslint/no-empty-object-type": "warn",
// Warn when the 'any' type is used explicitly
"@typescript-eslint/no-explicit-any": "warn",
// Warn when using require() instead of ES6 imports
"@typescript-eslint/no-require-imports": "warn",
// Disable the built-in no-unused-vars rule as unused-imports will handle it
"@typescript-eslint/no-unused-vars": "off",
// Configure unused-imports plugin for auto-fixing
"unused-imports/no-unused-imports": "warn",
"unused-imports/no-unused-vars": [
"warn",
{
varsIgnorePattern: "^_", // Ignore variables starting with _
argsIgnorePattern: "^_", // Ignore parameters starting with _
caughtErrorsIgnorePattern: "^_", // Ignore catch clause errors starting with _
ignoreRestSiblings: true,
},
],
},
},
// Seed files: allow control character regex (intentional sanitization for PostgreSQL)
{
files: ["prisma/seedFiles/**/*.ts"],
rules: {
"no-control-regex": "off",
},
},
// k6 load scripts. These never run under Node or in a browser — the k6
// runtime injects `__ENV`, `__VU` and `__ITER` as globals and resolves the
// `k6/*` module specifiers itself. Without this block every script reports
// `no-undef` on those three names, which is how `load-tests/smoke.js` came
// to carry four standing ESLint errors.
{
files: ["load-tests/**/*.js"],
languageOptions: {
globals: {
__ENV: "readonly",
__VU: "readonly",
__ITER: "readonly",
},
},
},
// Layering: `app/` is the routing layer. It may depend on lib, components,
// hooks, types and schemas — never the reverse.
//
// This regressed silently more than once before it was enforced. A shared
// component ended up importing a calendar and a slot-allocation hook from
// `app/dashboard/consultant/[consultantId]/(features)/shared/`, through a
// dynamic route segment; `lib/dashboard-queries.ts` pulled types out of two
// route folders; and `lib/data/explore-programs.ts` imported live functions
// from `app/explore`. Each was reasonable in isolation and each made the
// importing layer impossible to reuse or extract without dragging routing
// along with it.
//
// If a route folder holds something genuinely shared, the answer is to move
// it out — that is where `components/scheduling`, `hooks/scheduling`,
// `lib/scheduling` and `lib/explore` came from. For an API response shape,
// put it in `schemas/` and let both sides derive from one Zod definition.
{
files: [
"lib/**/*.{ts,tsx}",
"components/**/*.{ts,tsx}",
"hooks/**/*.{ts,tsx}",
"types/**/*.{ts,tsx}",
"schemas/**/*.{ts,tsx}",
],
rules: {
"no-restricted-imports": [
"error",
{
patterns: [
{
// Both spellings. The alias form is what anyone would normally
// write, but `../../app/...` resolves to exactly the same module
// and would have walked straight past an alias-only rule.
group: [
"@/app/*",
"@/app/**",
"**/app/dashboard/**",
"**/app/api/**",
"**/app/explore/**",
],
message:
"Do not import from app/ here — app/ is the routing layer and must depend on these layers, not the reverse. Move the shared code into lib/, components/, hooks/ or types/, or put the response shape in schemas/ and derive both sides from it.",
},
],
},
],
},
},
// Session freshness (#1807): a bare getSession() in server code reads the
// ~5-minute cookie cache, honouring demotions, bans, revocations and
// DPDP-erasures late. PII/finance/role-gated reads must be force-fresh.
// Remaining bare calls outside these globs are cosmetic reads pending the
// Phase-2 bulk pass — they are grandfathered here, not approved.
{
files: ["app/api/**/*.ts", "lib/**/*.ts"],
rules: {
"no-restricted-syntax": [
"error",
{
selector: "CallExpression[callee.name='getSession'][arguments.length=0]",
message:
"Bare getSession() serves the cookie cache (stale role/ban up to ~5 min). Use getSession(true) for force-fresh reads, requireApiAuth()/requireBackofficeSurface() in routes, or the explicit getCachedSession() for hot cosmetic reads. See #1807.",
},
{
// getSession(false) / getSession(undefined) are the same cached
// read spelled explicitly — they bypass the zero-arg selector.
selector:
"CallExpression[callee.name='getSession'][arguments.length=1][arguments.0.value=false]",
message:
"getSession(false) is the cookie-cached read: use the explicit getCachedSession() so the choice is greppable, or getSession(true). See #1807.",
},
{
// Identifier-only: a type gate is required because esquery matches
// a missing `name` attribute against the string 'undefined'.
selector:
"CallExpression[callee.name='getSession'][arguments.length=1][arguments.0.type='Identifier'][arguments.0.name='undefined']",
message:
"getSession(undefined) is the cookie-cached read: use the explicit getCachedSession() so the choice is greppable, or getSession(true). See #1807.",
},
],
},
},
];