Add React performance lint rules (#4443)

This commit is contained in:
Neil 2026-06-01 22:01:27 -07:00 committed by GitHub
parent c60fe4ebd7
commit e62cd5aefb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 37 additions and 14 deletions

View File

@ -22,9 +22,11 @@
"react/jsx-filename-extension": ["error", { "extensions": [".tsx", ".jsx"] }],
"react/jsx-fragments": "error",
"react/jsx-key": "error",
"react/jsx-no-constructed-context-values": "error",
"react/jsx-no-target-blank": "error",
"react/jsx-no-useless-fragment": ["error", { "allowExpressions": true }],
"react/jsx-pascal-case": "error",
"react/no-object-type-as-default-prop": "error",
"react/self-closing-comp": "error",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",

View File

@ -55,6 +55,8 @@ type FileContent = {
loadError?: string
}
const noopCloseMarkdownTableOfContents = (): void => {}
function matchesPendingEditorReveal(
reveal: PendingEditorReveal | null,
file: Pick<OpenFile, 'id' | 'filePath'>
@ -106,7 +108,7 @@ export function EditorContent({
isChangesMode,
sideBySide,
showMarkdownTableOfContents = false,
onCloseMarkdownTableOfContents = () => {},
onCloseMarkdownTableOfContents = noopCloseMarkdownTableOfContents,
markdownAnnotationsEnabled = true,
pendingEditorReveal,
handleContentChange,

View File

@ -89,6 +89,8 @@ import { findWorktreeById } from '@/store/slices/worktree-helpers'
import { dirname } from '@/lib/path'
import { relativePathInsideRoot } from '../../../../shared/cross-platform-path'
const EMPTY_MARKDOWN_DOCUMENTS: MarkdownDocument[] = []
type MarkdownPreviewProps = {
content: string
filePath: string
@ -434,7 +436,7 @@ export default function MarkdownPreview({
initialAnchor = null,
showTableOfContents = false,
onCloseTableOfContents,
markdownDocuments = [],
markdownDocuments = EMPTY_MARKDOWN_DOCUMENTS,
onOpenDocument,
markdownAnnotationsEnabled = false
}: MarkdownPreviewProps): React.JSX.Element {

View File

@ -43,6 +43,8 @@ import { getCodexAccountAuthWarning } from './codex-account-auth-warning'
export { ACCOUNTS_PANE_SEARCH_ENTRIES }
const EMPTY_WSL_DISTROS: string[] = []
type AccountsPaneProps = {
settings: GlobalSettings
updateSettings: (updates: Partial<GlobalSettings>) => void
@ -234,7 +236,7 @@ export function AccountsPane({
updateSettings,
wslSupportedPlatform = false,
wslAvailable = false,
wslDistros = [],
wslDistros = EMPTY_WSL_DISTROS,
wslCapabilitiesLoading = false
}: AccountsPaneProps): React.JSX.Element {
const searchQuery = useAppStore((s) => s.settingsSearchQuery)

View File

@ -2,6 +2,8 @@ import type { GlobalSettings } from '../../../../shared/types'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
import { SettingsRow, SettingsSegmentedControl } from './SettingsFormControls'
const EMPTY_WSL_DISTROS: string[] = []
type AgentDetectionRuntime = {
runtime: 'host' | 'wsl'
wslDistro?: string | null
@ -56,7 +58,7 @@ export function AgentLocationSetting({
refresh,
wslSupportedPlatform = false,
wslAvailable = false,
wslDistros = [],
wslDistros = EMPTY_WSL_DISTROS,
wslCapabilitiesLoading = false
}: AgentLocationSettingProps): React.JSX.Element | null {
const agentRuntime = getSelectedAgentRuntime(

View File

@ -30,6 +30,8 @@ import {
export { AGENTS_PANE_SEARCH_ENTRIES } from './agents-search'
const EMPTY_WSL_DISTROS: string[] = []
type AgentsPaneProps = {
settings: GlobalSettings
updateSettings: (updates: Partial<GlobalSettings>) => void | Promise<void>
@ -360,7 +362,7 @@ export function AgentsPane({
updateSettings,
wslSupportedPlatform = false,
wslAvailable = false,
wslDistros = [],
wslDistros = EMPTY_WSL_DISTROS,
wslCapabilitiesLoading = false
}: AgentsPaneProps): React.JSX.Element {
const { detectedIds: detectedList, isRefreshing, refresh } = useDetectedAgents()

View File

@ -36,6 +36,8 @@ import { ManageSessionsSection } from './ManageSessionsSection'
import { OSC52_CLIPBOARD_SETTING_ID } from '../terminal-pane/osc52-clipboard-setting-anchor'
import { WINDOWS_GIT_BASH_SHELL } from '../../../../shared/windows-terminal-shell'
const EMPTY_WSL_DISTROS: string[] = []
type TerminalPaneProps = {
settings: GlobalSettings
updateSettings: (updates: Partial<GlobalSettings>) => void
@ -59,7 +61,7 @@ export function TerminalPane({
scrollbackMode,
setScrollbackMode,
wslAvailable,
wslDistros = [],
wslDistros = EMPTY_WSL_DISTROS,
wslCapabilitiesLoading = false,
pwshAvailable,
gitBashAvailable = false

View File

@ -203,10 +203,12 @@ const WorktreeContextMenu = React.memo(function WorktreeContextMenu({
worktree,
children,
contentClassName,
selectedWorktrees = [worktree],
selectedWorktrees,
onContextMenuSelect,
onOpenChange
}: Props) {
const defaultSelectedWorktrees = useMemo(() => [worktree], [worktree])
const effectiveSelectedWorktrees = selectedWorktrees ?? defaultSelectedWorktrees
const updateWorktreeMeta = useAppStore((s) => s.updateWorktreeMeta)
const workspaceStatuses = useAppStore((s) => s.workspaceStatuses)
const openModal = useAppStore((s) => s.openModal)
@ -217,7 +219,8 @@ const WorktreeContextMenu = React.memo(function WorktreeContextMenu({
const deleteState = useAppStore((s) => s.deleteStateByWorktreeId[worktree.id])
const [menuOpen, setMenuOpen] = useState(false)
const [menuPoint, setMenuPoint] = useState({ x: 0, y: 0 })
const [contextWorktrees, setContextWorktrees] = useState<readonly Worktree[]>(selectedWorktrees)
const [contextWorktrees, setContextWorktrees] =
useState<readonly Worktree[]>(effectiveSelectedWorktrees)
const [createGroupDialogOpen, setCreateGroupDialogOpen] = useState(false)
const isDeleting = deleteState?.isDeleting ?? false
const repoMap = useRepoMap()
@ -230,7 +233,7 @@ const WorktreeContextMenu = React.memo(function WorktreeContextMenu({
const deleteStateByWorktreeId = useAppStore((s) => s.deleteStateByWorktreeId)
const scopeRef = useRef<HTMLDivElement>(null)
const contextMenuOpenedAtRef = useRef<number | null>(null)
const activeContextWorktrees = menuOpen ? contextWorktrees : selectedWorktrees
const activeContextWorktrees = menuOpen ? contextWorktrees : effectiveSelectedWorktrees
const isMultiContext = activeContextWorktrees.length > 1
const sleepableWorktrees = useMemo(
() =>
@ -469,7 +472,7 @@ const WorktreeContextMenu = React.memo(function WorktreeContextMenu({
event.preventDefault()
contextMenuOpenedAtRef.current = Date.now()
window.dispatchEvent(new Event(CLOSE_ALL_CONTEXT_MENUS_EVENT))
setContextWorktrees(onContextMenuSelect?.(event) ?? selectedWorktrees)
setContextWorktrees(onContextMenuSelect?.(event) ?? effectiveSelectedWorktrees)
const bounds = event.currentTarget.getBoundingClientRect()
setMenuPoint({ x: event.clientX - bounds.left, y: event.clientY - bounds.top })
setMenuOpenState(true)

View File

@ -722,7 +722,7 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
reorderRepos,
prCache,
workspaceStatuses,
projectGroups = [],
projectGroups = EMPTY_PROJECT_GROUPS,
onMoveWorktreeToStatus,
onMoveWorktreesToStatus,
onPinWorktree,

View File

@ -16,6 +16,8 @@ import {
} from './tab-agent-launch-options'
import type { TuiAgent } from '../../../../shared/types'
const EMPTY_AGENT_OPTIONS: readonly TabAgentLaunchOption[] = []
type TabBarCreateEntryProps = {
agentOptions?: readonly TabAgentLaunchOption[]
groupId: string
@ -28,7 +30,7 @@ type TabBarCreateEntryProps = {
}
export default function TabBarCreateEntry({
agentOptions = [],
agentOptions = EMPTY_AGENT_OPTIONS,
groupId,
menuOpen,
onDidOpenEntry,

View File

@ -50,6 +50,8 @@ type TerminalQuickCommandDialogProps = {
onSave: (command: TerminalQuickCommand) => void
}
const EMPTY_REPOS: Pick<Repo, 'id' | 'displayName' | 'path' | 'badgeColor'>[] = []
export function createTerminalQuickCommandDraft(
scope: TerminalQuickCommandScope = { type: 'global' }
): TerminalQuickCommand {
@ -66,7 +68,7 @@ export function TerminalQuickCommandDialog({
open,
mode,
command,
repos = [],
repos = EMPTY_REPOS,
onOpenChange,
onSave
}: TerminalQuickCommandDialogProps): React.JSX.Element {

View File

@ -28,6 +28,8 @@ function ToggleGroup({
VariantProps<typeof toggleVariants> & {
spacing?: number
}) {
const contextValue = React.useMemo(() => ({ variant, size, spacing }), [variant, size, spacing])
return (
<ToggleGroupPrimitive.Root
data-slot="toggle-group"
@ -41,7 +43,7 @@ function ToggleGroup({
)}
{...props}
>
<ToggleGroupContext.Provider value={{ variant, size, spacing }}>
<ToggleGroupContext.Provider value={contextValue}>
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>