feat(frontend): add prefer-optional-chain ESLint rule and apply fixes (#12073)

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Xingyao Wang 2025-12-18 08:42:52 -06:00 committed by GitHub
parent afce58a27d
commit aff9d69d41
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 18 additions and 19 deletions

View File

@ -18,6 +18,8 @@
"i18next/no-literal-string": "error",
"unused-imports/no-unused-imports": "error",
"prettier/prettier": ["error"],
// Enforce using optional chaining (?.) instead of && chains for null/undefined checks
"@typescript-eslint/prefer-optional-chain": "error",
// Resolves https://stackoverflow.com/questions/59265981/typescript-eslint-missing-file-extension-ts-import-extensions/59268871#59268871
"import/extensions": [
"error",

View File

@ -12,10 +12,9 @@ export function BrowserPanel() {
reset();
}, [conversationId, reset]);
const imgSrc =
screenshotSrc && screenshotSrc.startsWith("data:image/png;base64,")
? screenshotSrc
: `data:image/png;base64,${screenshotSrc || ""}`;
const imgSrc = screenshotSrc?.startsWith("data:image/png;base64,")
? screenshotSrc
: `data:image/png;base64,${screenshotSrc ?? ""}`;
return (
<div className="h-full w-full flex flex-col text-neutral-400">

View File

@ -140,7 +140,7 @@ const getTaskTrackingObservationContent = (
content += "\n\n**Task List:** Empty";
}
if (event.content && event.content.trim()) {
if (event.content?.trim()) {
content += `\n\n**Result:** ${event.content.trim()}`;
}

View File

@ -192,8 +192,7 @@ export const Messages: React.FC<MessagesProps> = React.memo(
) => {
const conversationInstructions = `Target file: ${target}\n\nDescription: ${query}\n\nTriggers: ${triggers.join(", ")}`;
if (
!conversation ||
!conversation.selected_repository ||
!conversation?.selected_repository ||
!conversation.selected_branch ||
!conversation.git_provider ||
!selectedEventId

View File

@ -75,7 +75,7 @@ export function GitProviderDropdown({
}
// If no input value, show all providers
if (!inputValue || !inputValue.trim()) {
if (!inputValue?.trim()) {
return providers;
}

View File

@ -99,7 +99,7 @@ export function GitRepoDropdown({
);
// If no input value, return all recent repos for this provider
if (!inputValue || !inputValue.trim()) {
if (!inputValue?.trim()) {
return providerFilteredRepos;
}
@ -139,7 +139,7 @@ export function GitRepoDropdown({
baseRepositories = repositories;
}
// If no input value, show all repositories
else if (!inputValue || !inputValue.trim()) {
else if (!inputValue?.trim()) {
baseRepositories = repositories;
}
// For URL inputs, use the processed search input for filtering
@ -246,8 +246,7 @@ export function GitRepoDropdown({
// Create sticky footer item for GitHub provider
const stickyFooterItem = useMemo(() => {
if (
!config ||
!config.APP_SLUG ||
!config?.APP_SLUG ||
provider !== ProviderOptions.github ||
config.APP_MODE !== "saas"
)

View File

@ -45,7 +45,7 @@ export function DropdownItem<T>({
// eslint-disable-next-line react/jsx-props-no-spreading
<li key={getItemKey(item)} {...itemProps}>
<div className="flex items-center gap-2">
{renderIcon && renderIcon(item)}
{renderIcon?.(item)}
<span className="font-medium">{getDisplayText(item)}</span>
</div>
</li>

View File

@ -5,7 +5,7 @@ export const parseMessageFromEvent = (event: MessageEvent): string => {
const message = event.llm_message;
// Safety check: ensure llm_message exists and has content
if (!message || !message.content) {
if (!message?.content) {
return "";
}

View File

@ -34,7 +34,7 @@ export const SECRETS_HANDLERS = [
http.post("/api/secrets", async ({ request }) => {
const body = (await request.json()) as CustomSecret;
if (typeof body === "object" && body && body.name) {
if (typeof body === "object" && body?.name) {
secrets.set(body.name, body);
return HttpResponse.json(true);
}
@ -48,7 +48,7 @@ export const SECRETS_HANDLERS = [
if (typeof id === "string" && typeof body === "object") {
const secret = secrets.get(id);
if (secret && body && body.name) {
if (secret && body?.name) {
const newSecret: CustomSecret = { ...secret, ...body };
secrets.delete(id);
secrets.set(body.name, newSecret);

View File

@ -134,7 +134,7 @@ export const SETTINGS_HANDLERS = [
const providerTokensSet: Partial<Record<Provider, string | null>> =
Object.fromEntries(
Object.entries(rawTokens)
.filter(([, val]) => val && val.token)
.filter(([, val]) => val?.token)
.map(([provider]) => [provider as Provider, ""]),
);

View File

@ -51,7 +51,7 @@ function VSCodeTab() {
);
}
if (error || (data && data.error) || !data?.url || iframeError) {
if (error || data?.error || !data?.url || iframeError) {
return (
<div className="w-full h-full flex items-center text-center justify-center text-2xl text-tertiary-light">
{iframeError ||

View File

@ -16,7 +16,7 @@ import {
* splitIsActuallyVersion(split) // returns true
*/
const splitIsActuallyVersion = (split: string[]) =>
split[1] && split[1][0] && isNumber(split[1][0]);
split[1]?.[0] && isNumber(split[1][0]);
/**
* Given a model string, extract the provider and model name. Currently the supported separators are "/" and "."