forked from OpenHands/OpenHands
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patheslint.config.js
More file actions
392 lines (374 loc) Β· 15.1 KB
/
Copy patheslint.config.js
File metadata and controls
392 lines (374 loc) Β· 15.1 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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
// Flat ESLint config (ESLint 9+).
//
// Migration notes (see commit history for full context):
// - Replaces the legacy `.eslintrc` file, which ESLint 9 deprecated and
// ESLint 10 will remove entirely.
// - Drops `eslint-config-airbnb` / `eslint-config-airbnb-typescript`: they
// pin ESLint to v7/v8 and `@typescript-eslint/*` to v7, so they block any
// future ESLint major bumps. The opinionated airbnb rules the codebase
// actually leaned on (no-param-reassign, import/extensions, etc.) are
// re-declared explicitly below, and the long list of airbnb rules the old
// config already turned off has just been deleted instead of re-disabled.
// - Replaces `eslint-plugin-import` (only declares an `^8 || ^9` peer) with
// the actively-maintained, flat-config-native fork `eslint-plugin-import-x`.
// The rule names are kept under the `import/` prefix below to minimise
// churn on existing `// eslint-disable-next-line import/...` comments.
// - `eslint-plugin-i18next` still ships only legacy config, so it is pulled
// in via `FlatCompat` from `@eslint/eslintrc`.
// - Targets ESLint 9, not 10. The original dependabot PR proposed eslint@10,
// but as of this commit `eslint-plugin-react` still calls
// `context.getFilename()` (removed in ESLint 10) at rule-load time, which
// makes a v10 bump explode on every file. Re-evaluate once that plugin
// ships an ESLint-10-compatible release.
import { fileURLToPath } from "node:url";
import path from "node:path";
import { FlatCompat } from "@eslint/eslintrc";
import js from "@eslint/js";
import tsParser from "@typescript-eslint/parser";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import reactPlugin from "eslint-plugin-react";
import reactHooksPlugin from "eslint-plugin-react-hooks";
import jsxA11yPlugin from "eslint-plugin-jsx-a11y";
import importXPlugin from "eslint-plugin-import-x";
import prettierPlugin from "eslint-plugin-prettier";
import prettierConfig from "eslint-config-prettier";
import unusedImportsPlugin from "eslint-plugin-unused-imports";
import tanstackQueryPlugin from "@tanstack/eslint-plugin-query";
import globals from "globals";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
resolvePluginsRelativeTo: __dirname,
});
/**
* ESLint rule: forbid raw `fetch` calls that target an agent-server `/api/...`
* path, so API access goes through the typed @openhands/typescript-client
* clients. Only statically-resolvable agent-server paths are flagged; external
* URLs, dynamic identifiers, and non-`/api/` draws (cookie-auth static
* fileserver, OAuth device verification) are left alone.
*/
function createNoDirectAgentServerFetchRule() {
return {
meta: {
type: "problem",
docs: {
description:
"Use typed @openhands/typescript-client clients instead of global fetch for agent-server API calls.",
},
messages: {
noRawFetch:
"Use a typed @openhands/typescript-client client (or AgentServerClient.request) instead of global fetch for agent-server API calls.",
},
},
create(context) {
function resolveStaticUrl(node) {
if (node.type === "Literal" && typeof node.value === "string") {
return node.value;
}
if (node.type === "TemplateLiteral") {
return node.quasis
.map((q) => q.value.raw)
.join("");
}
return null;
}
return {
CallExpression(node) {
const callee = node.callee;
if (callee.type !== "Identifier" || callee.name !== "fetch") return;
const urlNode = node.arguments[0];
if (!urlNode) return;
const url = resolveStaticUrl(urlNode);
if (url === null || !url.includes("/api/")) return;
context.report({ node, messageId: "noRawFetch" });
},
};
},
};
}
export default [
// Files / dirs we never want to lint.
{
ignores: [
"node_modules/**",
"dist/**",
"build/**",
"coverage/**",
".react-router/**",
"playwright-report/**",
"playwright-report-live/**",
"test-results/**",
"test-results-live/**",
"public/mockServiceWorker.js",
// Self-contained browser ES module served to extensions at runtime;
// not part of the TypeScript project.
"src/fixtures/canvas-extensions/**/*.js",
"src/i18n/declaration.d.ts",
],
},
// Base JS recommended rules.
js.configs.recommended,
// `eslint-plugin-import-x` ships flat configs directly. The rules are
// registered under both `import-x/*` and `import/*` aliases so existing
// `// eslint-disable-next-line import/*` comments still match.
importXPlugin.flatConfigs.recommended,
importXPlugin.flatConfigs.typescript,
// `eslint-plugin-i18next` is still eslintrc-only β pull it in via compat.
...compat.extends("plugin:i18next/recommended"),
// Project-wide settings + React/TS/etc. rules for source files.
{
files: ["**/*.{ts,tsx,js,jsx,mjs,cjs}"],
// Surface (but don't fail on) `// eslint-disable-next-line ...` directives
// that target rules this config no longer enables. The codebase has ~50
// such directives left over from the airbnb era; "warn" lets us clean
// them up incrementally instead of either failing CI on day one or
// letting new stale directives accumulate silently.
linterOptions: {
reportUnusedDisableDirectives: "warn",
},
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
parser: tsParser,
parserOptions: {
project: "./tsconfig.json",
ecmaFeatures: { jsx: true },
},
globals: {
...globals.browser,
...globals.node,
...globals.es2024,
},
},
plugins: {
"@typescript-eslint": tsPlugin,
react: reactPlugin,
"react-hooks": reactHooksPlugin,
"jsx-a11y": jsxA11yPlugin,
"@tanstack/query": tanstackQueryPlugin,
"unused-imports": unusedImportsPlugin,
// Alias `import-x` rules under `import/*` so existing
// `// eslint-disable-next-line import/foo` comments keep working.
import: importXPlugin,
prettier: prettierPlugin,
// Local rule enforcing that agent-server API calls go through the typed
// @openhands/typescript-client clients instead of a raw `fetch`.
local: {
rules: {
"no-direct-agent-server-fetch":
createNoDirectAgentServerFetchRule(),
},
},
},
settings: {
react: { version: "detect" },
// `eslint-import-resolver-typescript` resolves both TS path aliases
// (via tsconfig) and regular node-style imports, so we don't also
// configure the legacy `node` resolver shortcut here.
"import-x/resolver": {
typescript: true,
},
},
rules: {
// Recommended rule packs we want everywhere.
...tsPlugin.configs["eslint-recommended"].overrides[0].rules,
...tsPlugin.configs.recommended.rules,
...reactPlugin.configs.recommended.rules,
...reactPlugin.configs["jsx-runtime"].rules,
...reactHooksPlugin.configs.recommended.rules,
...jsxA11yPlugin.configs.recommended.rules,
...tanstackQueryPlugin.configs.recommended.rules,
// Prettier integration. `eslint-config-prettier` turns off conflicting
// stylistic rules; `eslint-plugin-prettier` re-reports prettier diffs
// as lint errors so `npm run lint` fails on unformatted code (matching
// the pre-flat-config behaviour).
...prettierConfig.rules,
"prettier/prettier": "error",
// Project conventions previously enforced via airbnb / custom rules.
// Lint JSX *attributes* (not just text between tags) for hard-coded
// user-facing strings. The plugin default (`mode: 'jsx-text-only'`)
// never checks attribute values, which let untranslated strings like
// `aria-label="Close"` / `placeholder="..."` slip past lint (cf. #1306).
//
// `jsx-only` checks every literal inside a JSX subtree, so we scope it:
// - jsx-attributes.include: only attributes that carry translatable
// text. Everything else (testId, name, color, to, href, className,
// data-*, β¦) is ignored automatically β no brittle deny-list.
// - callees/object-properties: re-list the plugin defaults (the option
// merge is shallow, so providing a key replaces it) and add
// `cn`/`className` so Tailwind class strings built via `cn(...)` or
// `{ className: "..." }` aren't flagged.
"i18next/no-literal-string": [
"error",
{
mode: "jsx-only",
"jsx-attributes": {
include: [
"placeholder",
"alt",
"aria-label",
"title",
"label",
"heading",
"text",
],
},
callees: {
exclude: [
"i18n(ext)?",
"t",
"require",
"addEventListener",
"removeEventListener",
"postMessage",
"getElementById",
"dispatch",
"commit",
"includes",
"indexOf",
"endsWith",
"startsWith",
"cn",
],
},
"object-properties": {
exclude: ["[A-Z_-]+", "className"],
},
},
],
"unused-imports/no-unused-imports": "error",
"@typescript-eslint/prefer-optional-chain": "error",
"no-restricted-imports": [
"error",
{
paths: [
{
name: "@openhands/typescript-client/client/http-client",
message:
"Use typed @openhands/typescript-client clients instead of constructing HttpClient directly.",
},
],
},
],
// All agent-server API access must go through the typed
// @openhands/typescript-client clients. A raw global `fetch` against an
// agent-server `/api/...` path bypasses the typed access layer, so it is
// banned here (see also src/api/no-direct-agent-server-calls.test.ts).
// Browser-cookie-auth and external (non-agent-server) fetches β e.g. the
// workspace static fileserver, OAuth device verification, and the npm
// registry version check β do not target `/api/...` and are unaffected.
"local/no-direct-agent-server-fetch": "error",
// Allow `interface Foo extends Bar<"foo"> {}` β the codebase uses this
// discriminated-union pattern in `src/types/agent-server/**` and the
// empty body is intentional. We still disallow `interface Foo {}` and
// `type Foo = {}` (the default behaviour for the other two options).
"@typescript-eslint/no-empty-object-type": [
"error",
{ allowInterfaces: "with-single-extends" },
],
// tsPlugin's `recommended` enables no-unused-vars; we already report
// unused imports above, so make sure the TS rule still catches unused
// locals/args (with the standard airbnb-style `_`-prefix escape hatch).
// `caughtErrors: 'none'` keeps the v7 behaviour of allowing
// `catch (error)` clauses where the error is intentionally unused.
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
args: "after-used",
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
ignoreRestSiblings: true,
caughtErrors: "none",
},
],
// React Compiler rules (added in eslint-plugin-react-hooks v5+) are
// opt-in for projects that have actually adopted the compiler. Keep
// them off so existing code isn't retroactively flagged.
"react-hooks/immutability": "off",
"react-hooks/preserve-manual-memoization": "off",
"react-hooks/refs": "off",
"react-hooks/set-state-in-effect": "off",
// Match the legacy config: don't require .ts/.tsx import extensions,
// but enforce them for everything else. See
// https://stackoverflow.com/q/59265981 for background.
"import-x/extensions": [
"error",
"ignorePackages",
{ "": "never", ts: "never", tsx: "never", js: "never", jsx: "never" },
],
// The old config inherited a bunch of import rules from airbnb that
// either don't apply or are now noise; explicitly turn off the ones
// most likely to fire on this codebase.
"import-x/prefer-default-export": "off",
"import-x/no-extraneous-dependencies": "off",
// `import-x/no-unresolved` is redundant with the TypeScript compiler:
// `tsc` (run as `npm run typecheck` before `eslint`) already fails on
// unresolved imports with much better error messages, and the rule
// has known false positives with `paths`/exports-map resolution even
// when the typescript resolver is configured. Keeping it on duplicates
// tsc errors and produces noise on Vite's `?url` / `?worker` import
// suffixes that ESLint can't see through.
"import-x/no-unresolved": "off",
// These two fire a lot of false positives on TypeScript projects that
// import a namespace and then call methods off it (`import api from
// './foo'; api.bar()` etc.). The TS compiler already catches anything
// truly wrong here.
"import-x/no-named-as-default": "off",
"import-x/no-named-as-default-member": "off",
},
},
// TypeScript-only overrides (re-applied airbnb-ish relaxations from the
// previous `.eslintrc` overrides block).
{
files: ["**/*.{ts,tsx}"],
ignores: ["src/hooks/query/query-keys.ts"],
rules: {
// Allow state mutation in reduce and Redux-style reducers.
"no-param-reassign": [
"error",
{
props: true,
ignorePropertyModificationsFor: ["acc", "state"],
},
],
"no-restricted-syntax": [
"error",
{
selector:
"Property[key.name='queryKey'] > ArrayExpression[elements.0.value='settings']",
message:
"Use SETTINGS_QUERY_KEYS helpers instead of raw settings query key arrays.",
},
{
selector:
"CallExpression[callee.name='t'] > Literal:first-child[value=/^[A-Z0-9_]+\\$/]",
message: "Use I18nKey instead of raw translation key strings.",
},
{
selector:
"CallExpression[callee.property.name='t'] > Literal:first-child[value=/^[A-Z0-9_]+\\$/]",
message: "Use I18nKey instead of raw translation key strings.",
},
],
"react/require-default-props": "off",
"no-underscore-dangle": "off",
"jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/click-events-have-key-events": "off",
// For https://github.com/airbnb/javascript/issues/1885
"jsx-a11y/label-has-associated-control": [
2,
{
required: {
some: ["nesting", "id"],
},
},
],
"react/prop-types": "off",
"react/no-array-index-key": "off",
"react-hooks/exhaustive-deps": "off",
"react/react-in-jsx-scope": "off",
},
},
];