Skip to content

Commit 57fa4c2

Browse files
authored
Upgrade ESLint v9 to v10 and bump dependencies (#814)
- Bump runtime and dev dependencies (axios, electron, typescript-eslint, etc.) - Upgrade ESLint from v9 to v10 and `@eslint/js` to v10 - Replace `eslint-plugin-react` + `eslint-plugin-react-hooks` with `@eslint-react/eslint-plugin` (4-7x faster, ESLint v10 compatible, type-checked rules) - Fix all new violations due to update
1 parent ea84a40 commit 57fa4c2

File tree

17 files changed

+699
-1432
lines changed

17 files changed

+699
-1432
lines changed

eslint.config.mjs

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,26 @@
11
// @ts-check
22
import eslint from "@eslint/js";
3-
import { defineConfig } from "eslint/config";
3+
import { defineConfig, globalIgnores } from "eslint/config";
44
import markdown from "@eslint/markdown";
55
import tseslint from "typescript-eslint";
66
import prettierConfig from "eslint-config-prettier";
77
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
88
import { flatConfigs as importXFlatConfigs } from "eslint-plugin-import-x";
99
import packageJson from "eslint-plugin-package-json";
10-
import reactPlugin from "eslint-plugin-react";
11-
import reactHooksPlugin from "eslint-plugin-react-hooks";
10+
import eslintReact from "@eslint-react/eslint-plugin";
1211
import globals from "globals";
1312

1413
export default defineConfig(
15-
// Global ignores
16-
{
17-
ignores: [
18-
"out/**",
19-
"dist/**",
20-
"packages/*/dist/**",
21-
"**/*.d.ts",
22-
"vitest.config.ts",
23-
"**/vite.config*.ts",
24-
"**/createWebviewConfig.ts",
25-
".vscode-test/**",
26-
],
27-
},
14+
globalIgnores([
15+
"out/**",
16+
"dist/**",
17+
"packages/*/dist/**",
18+
"**/*.d.ts",
19+
"vitest.config.ts",
20+
"**/vite.config*.ts",
21+
"**/createWebviewConfig.ts",
22+
".vscode-test/**",
23+
]),
2824

2925
// Base ESLint recommended rules (for JS/TS/TSX files only)
3026
{
@@ -176,38 +172,31 @@ export default defineConfig(
176172
},
177173
},
178174

179-
// React hooks and compiler rules (covers .ts hook files too)
175+
// React rules with type-checked analysis (covers hooks, JSX, DOM)
180176
{
181177
files: ["packages/**/*.{ts,tsx}"],
182-
...reactHooksPlugin.configs.flat.recommended,
178+
extends: [eslintReact.configs["recommended-type-checked"]],
183179
rules: {
184-
...reactHooksPlugin.configs.flat.recommended.rules,
185180
// React Compiler auto-memoizes; exhaustive-deps false-positives on useCallback
186-
"react-hooks/exhaustive-deps": "off",
181+
"@eslint-react/exhaustive-deps": "off",
187182
},
188183
},
189184

190-
// TSX files - React JSX rules
185+
// Package.json linting
186+
packageJson.configs.recommended,
191187
{
192-
files: ["**/*.tsx"],
193-
plugins: {
194-
react: reactPlugin,
195-
},
196-
settings: {
197-
react: {
198-
version: "detect",
199-
},
200-
},
188+
// The root package.json is a VS Code extension (not an npm package),
189+
// so these publishing-oriented rules don't apply.
190+
files: ["package.json"],
191+
ignores: ["packages/**/package.json"],
201192
rules: {
202-
...reactPlugin.configs.recommended.rules,
203-
...reactPlugin.configs["jsx-runtime"].rules, // React 17+ JSX transform
204-
"react/prop-types": "off", // Using TypeScript
193+
"package-json/require-exports": "off",
194+
"package-json/require-files": "off",
195+
"package-json/require-sideEffects": "off",
196+
"package-json/require-attribution": "off",
205197
},
206198
},
207199

208-
// Package.json linting
209-
packageJson.configs.recommended,
210-
211200
// Markdown linting with GitHub-flavored admonitions allowed
212201
...markdown.configs.recommended,
213202
{

package.json

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -473,23 +473,24 @@
473473
"dependencies": {
474474
"@peculiar/x509": "^1.14.3",
475475
"@repo/shared": "workspace:*",
476-
"axios": "1.13.5",
476+
"axios": "1.13.6",
477477
"date-fns": "^4.1.0",
478478
"eventsource": "^4.1.0",
479-
"find-process": "^2.0.0",
479+
"find-process": "^2.1.0",
480480
"jsonc-parser": "^3.3.1",
481481
"openpgp": "^6.3.0",
482482
"pretty-bytes": "^7.1.0",
483483
"proper-lockfile": "^4.1.2",
484484
"proxy-agent": "^6.5.0",
485485
"semver": "^7.7.4",
486-
"strip-ansi": "^7.1.2",
486+
"strip-ansi": "^7.2.0",
487487
"ua-parser-js": "^1.0.41",
488488
"ws": "^8.19.0",
489489
"zod": "^4.3.6"
490490
},
491491
"devDependencies": {
492-
"@eslint/js": "^9.39.2",
492+
"@eslint-react/eslint-plugin": "^2.13.0",
493+
"@eslint/js": "^10.0.1",
493494
"@eslint/markdown": "^7.5.1",
494495
"@tanstack/react-query": "catalog:",
495496
"@testing-library/jest-dom": "^6.9.1",
@@ -504,8 +505,8 @@
504505
"@types/ua-parser-js": "0.7.39",
505506
"@types/vscode": "^1.95.0",
506507
"@types/ws": "^8.18.1",
507-
"@typescript-eslint/eslint-plugin": "^8.56.0",
508-
"@typescript-eslint/parser": "^8.56.0",
508+
"@typescript-eslint/eslint-plugin": "^8.56.1",
509+
"@typescript-eslint/parser": "^8.56.1",
509510
"@vitejs/plugin-react": "catalog:",
510511
"@vitest/coverage-v8": "^4.0.18",
511512
"@vscode/test-cli": "^0.0.12",
@@ -516,32 +517,30 @@
516517
"coder": "catalog:",
517518
"concurrently": "^9.2.1",
518519
"dayjs": "^1.11.19",
519-
"electron": "^40.4.1",
520+
"electron": "^40.6.1",
520521
"esbuild": "^0.27.3",
521-
"eslint": "^9.39.2",
522+
"eslint": "^10.0.2",
522523
"eslint-config-prettier": "^10.1.8",
523524
"eslint-import-resolver-typescript": "^4.4.4",
524525
"eslint-plugin-import-x": "^4.16.1",
525-
"eslint-plugin-package-json": "^0.88.3",
526-
"eslint-plugin-react": "^7.37.5",
527-
"eslint-plugin-react-hooks": "^7.0.1",
528-
"globals": "^17.3.0",
526+
"eslint-plugin-package-json": "^0.89.2",
527+
"globals": "^17.4.0",
529528
"jsdom": "^28.1.0",
530-
"jsonc-eslint-parser": "^2.4.2",
529+
"jsonc-eslint-parser": "^3.1.0",
531530
"memfs": "^4.56.10",
532531
"prettier": "^3.8.1",
533532
"react": "catalog:",
534533
"react-dom": "catalog:",
535534
"typescript": "catalog:",
536-
"typescript-eslint": "^8.56.0",
535+
"typescript-eslint": "^8.56.1",
537536
"utf-8-validate": "^6.0.6",
538537
"vite": "catalog:",
539538
"vitest": "^4.0.18"
540539
},
541540
"extensionPack": [
542541
"ms-vscode-remote.remote-ssh"
543542
],
544-
"packageManager": "pnpm@10.29.2",
543+
"packageManager": "pnpm@10.30.3",
545544
"engines": {
546545
"vscode": "^1.95.0",
547546
"node": ">= 20"

packages/tasks/src/components/ActionMenu.tsx

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -74,39 +74,44 @@ export function ActionMenu({ items }: ActionMenuProps) {
7474
tabIndex={-1}
7575
onKeyDown={(e) => isEscape(e) && close()}
7676
>
77-
{items.map((item, index) =>
78-
item.separator ? (
79-
<div
80-
key={`sep-${index}`}
81-
className="action-menu-separator"
82-
role="separator"
83-
/>
84-
) : (
85-
<button
86-
key={`${item.label}-${index}`}
87-
type="button"
88-
className={[
89-
"action-menu-item",
90-
item.danger && "danger",
91-
item.loading && "loading",
92-
]
93-
.filter(Boolean)
94-
.join(" ")}
95-
onClick={() => {
96-
item.onClick();
97-
close();
98-
}}
99-
disabled={item.disabled === true || item.loading === true}
100-
>
101-
{item.loading ? (
102-
<VscodeProgressRing className="action-menu-spinner" />
103-
) : (
104-
<VscodeIcon name={item.icon} className="action-menu-icon" />
105-
)}
106-
<span>{item.label}</span>
107-
</button>
108-
),
109-
)}
77+
{items
78+
.map((item, i) => ({
79+
item,
80+
key: item.separator ? `separator-${i}` : `${item.label}-${i}`,
81+
}))
82+
.map(({ item, key }) =>
83+
item.separator ? (
84+
<div
85+
key={key}
86+
className="action-menu-separator"
87+
role="separator"
88+
/>
89+
) : (
90+
<button
91+
key={key}
92+
type="button"
93+
className={[
94+
"action-menu-item",
95+
item.danger && "danger",
96+
item.loading && "loading",
97+
]
98+
.filter(Boolean)
99+
.join(" ")}
100+
onClick={() => {
101+
item.onClick();
102+
close();
103+
}}
104+
disabled={item.disabled === true || item.loading === true}
105+
>
106+
{item.loading ? (
107+
<VscodeProgressRing className="action-menu-spinner" />
108+
) : (
109+
<VscodeIcon name={item.icon} className="action-menu-icon" />
110+
)}
111+
<span>{item.label}</span>
112+
</button>
113+
),
114+
)}
110115
</div>
111116
)}
112117
</div>

packages/tasks/src/components/WorkspaceLogs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function WorkspaceLogs({ task }: { task: Task }) {
1919
{lines.length === 0 ? (
2020
<LogViewerPlaceholder>Waiting for logs...</LogViewerPlaceholder>
2121
) : (
22-
lines.map((line, i) => <LogLine key={i}>{line}</LogLine>)
22+
lines.map((entry) => <LogLine key={entry.id}>{entry.text}</LogLine>)
2323
)}
2424
</LogViewer>
2525
);

packages/tasks/src/hooks/useFollowScroll.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ interface ScrollableElement extends HTMLElement {
1717
*/
1818
export function useFollowScroll(): RefObject<HTMLDivElement | null> {
1919
const ref = useRef<HTMLDivElement>(null);
20-
const atBottom = useRef(true);
20+
const atBottomRef = useRef(true);
2121

2222
useEffect(() => {
2323
const sentinel = ref.current;
@@ -28,7 +28,7 @@ export function useFollowScroll(): RefObject<HTMLDivElement | null> {
2828
const container = parent as ScrollableElement;
2929

3030
function onScroll() {
31-
atBottom.current =
31+
atBottomRef.current =
3232
container.scrollMax - container.scrollPos <= BOTTOM_THRESHOLD;
3333
}
3434

@@ -41,7 +41,7 @@ export function useFollowScroll(): RefObject<HTMLDivElement | null> {
4141
});
4242

4343
const mo = new MutationObserver(() => {
44-
if (atBottom.current) {
44+
if (atBottomRef.current) {
4545
scrollToBottom();
4646
}
4747
});

packages/tasks/src/hooks/useWorkspaceLogs.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@ import { useEffect, useState } from "react";
22

33
import { useTasksApi } from "./useTasksApi";
44

5+
export interface LogEntry {
6+
id: number;
7+
text: string;
8+
}
9+
510
/**
611
* Subscribes to workspace log lines pushed from the extension.
712
* Batches updates per animation frame to avoid excessive re-renders
813
* when many lines arrive in quick succession.
914
*/
10-
export function useWorkspaceLogs(): string[] {
15+
export function useWorkspaceLogs(): LogEntry[] {
1116
const { onWorkspaceLogsAppend, stopStreamingWorkspaceLogs } = useTasksApi();
12-
const [lines, setLines] = useState<string[]>([]);
17+
const [lines, setLines] = useState<LogEntry[]>([]);
1318

1419
useEffect(() => {
1520
let pending: string[] = [];
@@ -22,7 +27,13 @@ export function useWorkspaceLogs(): string[] {
2227
const batch = pending;
2328
pending = [];
2429
frame = 0;
25-
setLines((prev) => prev.concat(batch));
30+
setLines((prev) => {
31+
const entries = batch.map((text, i) => ({
32+
id: prev.length + i,
33+
text,
34+
}));
35+
return prev.concat(entries);
36+
});
2637
});
2738
}
2839
});

packages/webview-shared/createWebviewConfig.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export function createWebviewConfig(
3030
target: "esnext",
3131
// Skip gzip size calculation for faster builds
3232
reportCompressedSize: false,
33+
// Webviews load as a single bundle; code-splitting doesn't apply
34+
chunkSizeWarningLimit: 600,
3335
rollupOptions: {
3436
// HTML is generated by the extension with CSP headers
3537
input: resolve(dirname, "src/index.tsx"),

packages/webview-shared/src/react/hooks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function useMessage<T>(handler: (message: T) => void): void {
2222
* Hook to manage webview state with VS Code's state API
2323
*/
2424
export function useVsCodeState<T>(initialState: T): [T, (state: T) => void] {
25-
const [state, setLocalState] = useState<T>((): T => {
25+
const [localState, setLocalState] = useState<T>((): T => {
2626
const saved = getState<T>();
2727
return saved ?? initialState;
2828
});
@@ -32,5 +32,5 @@ export function useVsCodeState<T>(initialState: T): [T, (state: T) => void] {
3232
setState(newState);
3333
}, []);
3434

35-
return [state, setVsCodeState];
35+
return [localState, setVsCodeState];
3636
}

0 commit comments

Comments
 (0)