-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Expand file tree
/
Copy patheslint.config.mjs
More file actions
169 lines (156 loc) · 9.65 KB
/
Copy patheslint.config.mjs
File metadata and controls
169 lines (156 loc) · 9.65 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
import { defineConfig } from "eslint/config";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
import pluginCompat from "eslint-plugin-compat";
// typed linting - rides eslint-config-next's own @typescript-eslint packages
import tsParser from "@typescript-eslint/parser";
import tsPlugin from "@typescript-eslint/eslint-plugin";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default defineConfig([{
/* Ours to declare: `lint` calls eslint directly (`next lint` supplied these, and is removed in
Next 16). Flat config only auto-skips node_modules - NOT dot-dirs. Only list what eslint
would otherwise open: js/ts under here would be linted with app rules and lie. */
ignores: [
".next*/**", "out/**", "dist/**", // build output (minified bundles trip compat/ on every modern API)
"public/**", // served verbatim, outside the bundler + type program
".claude/**", // agent tooling, own runtime
"electron/**", // excluded from the type program (root tsconfig)
"tools/video/**", // self-contained package, own toolchain
],
}, {
// // eslint's own base layer (no-dupe-keys, no-cond-assign, no-fallthrough, ...) - next/core-web-vitals
// // does NOT include it; until 2026-07 `js` was only imported as FlatCompat's resolver, never applied
// ...js.configs.recommended,
// rules: {
// ...js.configs.recommended.rules,
// // floods on TS (unused params in signatures, `_exhaustiveCheck`, catch bindings); tsc is the checker here
// "no-unused-vars": "off",
// // unbraced-case `const` is house style; tsc already errors on the dangerous cases (cross-case redeclare, TDZ reads)
// "no-case-declarations": "off",
// },
// }, {
// ...minus the core rules the TS compiler already enforces (no-undef, no-dupe-keys, no-redeclare, ...):
// they false-positive on TS constructs. Scoped to **/*.ts(x) by the config itself.
...tsPlugin.configs['flat/eslint-recommended'],
rules: {
...tsPlugin.configs['flat/eslint-recommended'].rules,
// NOTE: We may work on this one, not sure if there's perf impact or it's just cosmetic/semantic
// disable prefer-const for now
"prefer-const": "off",
},
}, {
extends: compat.extends("next/core-web-vitals"),
rules: {
// intentional empty catches (fire-and-forget cleanup) are house style
"no-empty": ["error", { allowEmptyCatch: true }],
// 2026-07-31 adoption backlog: base-layer rules with real findings at adoption time; promote each to error (default) as its count hits zero
// "prefer-const": "warn", // 88 - one `eslint . --fix` commit
// "no-useless-escape": "warn", // 11 - over-escaped chars in strings/regexes
// "no-extra-boolean-cast": "warn", // 8 - auto-fixable
// "no-prototype-builtins": "warn", // x.hasOwnProperty(y) -> Object.hasOwn(x, y)
"no-async-promise-executor": "warn", // async executor swallows rejections (promise never settles)
"no-unsafe-optional-chaining": "warn", // ?. when already guarded for
"no-empty-pattern": "warn", // ({})
"valid-typeof": "warn", // typeof X must be compared to a string literal
"no-constant-binary-expression": "warn",// fixed with above
"no-control-regex": "warn", // NUL-byte sanitizer regex (annottated)
"prefer-rest-params": "warn", // do not use `arguments` in TS
// Big-AGI hook
"react-hooks/exhaustive-deps": ["warn", {
additionalHooks: "(useMemoShallowStable)",
}],
// older-browser guard: we deliberately support sub-floor engines (Chrome 109/Win7, old Brave)
// for a few APIs, so ban the ones that crash there. eslint-plugin-compat can't catch these:
// they're at/below the browserslist floor (Chrome 110), so it considers them "supported".
"no-restricted-syntax": ["warn", {
selector: "CallExpression[callee.property.name=/^(toSorted|toReversed|toSpliced)$/]",
message: "ES2023 array method crashes on Chrome <110 (Win7/8) and old Brave. Use a copy + in-place form instead: [...arr].sort() / [...arr].reverse(), or arr.filter() instead of toSpliced().",
}, {
selector: "CallExpression[callee.property.name='with'][arguments.length=2]",
message: "Array.prototype.with() crashes on Chrome <110 (Win7/8) and old Brave. Use arr.map((v, i) => i === idx ? value : v), or a copy + index assignment.",
}, {
selector: "NewExpression[callee.object.name='Intl'][callee.property.name='Segmenter']",
message: "Intl.Segmenter is absent on older engines and throws. Call textIsSingleEmoji() (which feature-detects + falls back), or guard with `if (Intl.Segmenter)` and provide a fallback.",
}],
},
}, {
// browser API compatibility guard (reads `browserslist` from package.json) - catches
// above-floor Web APIs (the next Promise.withResolvers) at lint time before they ship.
...pluginCompat.configs["flat/recommended"],
settings: {
// feature-detected in-code (fallback/guard present), so they don't break older browsers
polyfills: ["requestIdleCallback", "Intl.Segmenter", "ClipboardItem"],
},
}, {
// Node-side code (tools/ scripts, the src tests that run under tsx): the browserslist floor
// does not apply - same split as the tools tsconfig project
files: ["tools/**", "**/*.test.ts"],
rules: {
"compat/compat": "off",
},
}, {
// Enrico 2026-07-30: TYPED rules - needs a full type program, so this is the ~15s of `npm run lint`
// unbound-method: detaching a prototype method (destructure, stored reference, callback) silently
// rebinds `this` - the destructure-heavy house style is only safe with this guard.
// Annotate genuinely 'detach-safe' declarations with `this: void` (or declare them property-style).
files: ["src/**/*.ts", "src/**/*.tsx"], // pages/ + app/ are in the program too, but hold ~no logic
ignores: ["**/*.test.ts"], // in the tools program, not this one
languageOptions: {
parser: tsParser,
parserOptions: { projectService: true, tsconfigRootDir: __dirname },
},
plugins: { "@typescript-eslint": tsPlugin },
rules: {
// a detached method (destructure, stored ref, callback) loses `this` at the call - attest detach-safe declarations with `this: void`, or declare them property-style
"@typescript-eslint/unbound-method": "warn",
// `delete arr[i]` leaves a hole (length unchanged) - use splice/filter
"@typescript-eslint/no-array-delete": "warn",
// for-in on arrays walks string keys (+ inherited props), not values - use for-of / .entries()
"@typescript-eslint/no-for-in-array": "warn",
// promises where values belong (conditionals, spreads); void-return check off - async onClick/handlers are idiomatic
"@typescript-eslint/no-misused-promises": ["warn", { checksVoidReturn: false }],
// NOTE: Useful
// // a switch over a union must name every member - a new union member with a forgotten arm is a silent distributed-contract bug; attest intentional partial switches with a `default`
// "@typescript-eslint/switch-exhaustiveness-check": ["warn", { considerDefaultExhaustiveForUnions: true }],
// NOTE: Not very useful, we are already okay with these - may replace the `fire/forget` text with `void`
// // un-awaited, un-`void`ed promise: rejections become unhandled (= PostHog noise); `void x()` attests fire-and-forget
// "@typescript-eslint/no-floating-promises": "warn",
// NOTE: Auto-Fix; this is just input noise
// // `as` that changes nothing - house rule ("no unnecessary TS casts") made mechanical; auto-fixable
// "@typescript-eslint/no-unnecessary-type-assertion": "warn",
// NOTE (5): 1 positive in AudioPlayer and a bunch of FPs
// // `${obj}` on a type without a real toString prints "[object Object]"
// "@typescript-eslint/no-base-to-string": "warn",
// NOTE (57): Must look into this - not sure they're all okay to auto-fix
// // `return promise` inside try escapes the catch; `return await` doesn't - auto-fixable
// "@typescript-eslint/return-await": ["warn", "in-try-catch"],
// NOTE (1): Shall fix
// // thrown non-Errors arrive stackless as `{}` through serializeError -> PostHog
// "@typescript-eslint/only-throw-error": "warn",
// NOTE (3): Shall fix
// // `await` on a non-thenable is a no-op typo
// "@typescript-eslint/await-thenable": "warn",
// IGNORE (44)
// // upgrade-debt radar: flags uses of @deprecated APIs, ours and dependencies'
// "@typescript-eslint/no-deprecated": "warn",
},
},
// // module-cycle detection (`npm run lint:cycles`) - full import-graph traversal, too slow for the
// // default lint; machine-checks what code comments enforce today ("value imports referenced only
// // inside function bodies"). Deliberate lazy-value cycles get an eslint-disable with rationale.
// // started by (tba) `"lint:cycles": "cross-env LINT_IMPORT_CYCLES=1 eslint src",` in package.json
// ...(process.env.LINT_IMPORT_CYCLES ? [{
// files: ["src/**/*.ts", "src/**/*.tsx"],
// rules: {
// "import/no-cycle": ["warn", { ignoreExternal: true }],
// },
// }] : []),
]);