chore: add more linting rules and fix warnings (#325)

This commit is contained in:
Nico 2026-01-08 18:53:02 +01:00 committed by GitHub
parent 76741d6fb8
commit 87403c9407
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 28 additions and 19 deletions

View file

@ -1,6 +1,6 @@
{ {
"$schema": "./node_modules/oxlint/configuration_schema.json", "$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["unicorn", "typescript", "oxc"], "plugins": ["eslint", "unicorn", "typescript", "oxc", "import", "react", "react-perf", "node", "jsx-a11y"],
"categories": {}, "categories": {},
"rules": { "rules": {
"constructor-super": "warn", "constructor-super": "warn",
@ -112,7 +112,8 @@
"unicorn/no-useless-length-check": "warn", "unicorn/no-useless-length-check": "warn",
"unicorn/no-useless-spread": "warn", "unicorn/no-useless-spread": "warn",
"unicorn/prefer-set-size": "warn", "unicorn/prefer-set-size": "warn",
"unicorn/prefer-string-starts-ends-with": "warn" "unicorn/prefer-string-starts-ends-with": "warn",
"import/no-cycle": "error"
}, },
"settings": { "settings": {
"jsx-a11y": { "jsx-a11y": {

View file

@ -489,18 +489,27 @@ interface ButtonProps {
const NodeButton = memo(({ depth, icon, onClick, onMouseEnter, className, children }: ButtonProps) => { const NodeButton = memo(({ depth, icon, onClick, onMouseEnter, className, children }: ButtonProps) => {
const paddingLeft = useMemo(() => `${8 + depth * NODE_PADDING_LEFT}px`, [depth]); const paddingLeft = useMemo(() => `${8 + depth * NODE_PADDING_LEFT}px`, [depth]);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onClick?.();
}
},
[onClick],
);
return ( return (
// biome-ignore lint/a11y/noStaticElementInteractions: we handle click and hover manually <button
// biome-ignore lint/a11y/useKeyWithClickEvents: we handle click and hover manually
<div
className={cn("flex items-center gap-2 w-full pr-2 text-sm py-1.5 text-left", className)} className={cn("flex items-center gap-2 w-full pr-2 text-sm py-1.5 text-left", className)}
style={{ paddingLeft }} style={{ paddingLeft }}
onClick={onClick} onClick={onClick}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onKeyDown={handleKeyDown}
> >
{icon} {icon}
<div className="truncate w-full flex items-center gap-2">{children}</div> <div className="truncate w-full flex items-center gap-2">{children}</div>
</div> </button>
); );
}); });

View file

@ -158,7 +158,7 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups }: Props) => {
} }
setSelectedIds(newSelected); setSelectedIds(newSelected);
}} }}
aria-label={`Select snapshot ${snapshot.short_id}`} aria-label={`Select snapshot ${snapshot.short_id}` as string}
/> />
</TableCell> </TableCell>
<TableCell className="font-mono text-sm"> <TableCell className="font-mono text-sm">

View file

@ -27,9 +27,9 @@ const Alert = React.forwardRef<
)); ));
Alert.displayName = "Alert"; Alert.displayName = "Alert";
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>( const AlertTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
<h5 ref={ref} className={cn("mb-1 font-medium leading-none tracking-tight", className)} {...props} /> <h5 ref={ref} className={cn("mb-1 font-medium leading-none tracking-tight", className)} {...props}>{props.children}</h5>
), ),
); );
AlertTitle.displayName = "AlertTitle"; AlertTitle.displayName = "AlertTitle";

View file

@ -43,16 +43,17 @@ function BreadcrumbLink({
); );
} }
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) { function BreadcrumbPage({ className, children, ...props }: React.ComponentProps<"a">) {
return ( return (
<span <a
data-slot="breadcrumb-page" data-slot="breadcrumb-page"
role="link"
aria-disabled="true" aria-disabled="true"
aria-current="page" aria-current="page"
className={cn("text-foreground font-normal truncate", className)} className={cn("text-foreground font-normal truncate", className)}
{...props} {...props}
/> >
{children}
</a>
); );
} }

View file

@ -88,7 +88,6 @@ export default function DownloadRecoveryKeyPage() {
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
placeholder="Enter your password" placeholder="Enter your password"
required required
autoFocus
disabled={downloadResticPassword.isPending} disabled={downloadResticPassword.isPending}
/> />
<p className="text-xs text-muted-foreground">Enter your account password to download the recovery key</p> <p className="text-xs text-muted-foreground">Enter your account password to download the recovery key</p>

View file

@ -84,7 +84,7 @@ export default function LoginPage() {
<FormItem> <FormItem>
<FormLabel>Username</FormLabel> <FormLabel>Username</FormLabel>
<FormControl> <FormControl>
<Input {...field} type="text" placeholder="admin" disabled={isLoggingIn} autoFocus /> <Input {...field} type="text" placeholder="admin" disabled={isLoggingIn} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>

View file

@ -115,7 +115,7 @@ export default function OnboardingPage() {
<FormItem> <FormItem>
<FormLabel>Username</FormLabel> <FormLabel>Username</FormLabel>
<FormControl> <FormControl>
<Input {...field} type="text" placeholder="admin" disabled={submitting} autoFocus /> <Input {...field} type="text" placeholder="admin" disabled={submitting} />
</FormControl> </FormControl>
<FormDescription>Choose a username for the admin account</FormDescription> <FormDescription>Choose a username for the admin account</FormDescription>
<FormMessage /> <FormMessage />

View file

@ -393,7 +393,7 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
type="button" type="button"
onClick={() => handleRemovePath(path)} onClick={() => handleRemovePath(path)}
className="ml-1 hover:bg-destructive/20 rounded p-0.5 transition-colors" className="ml-1 hover:bg-destructive/20 rounded p-0.5 transition-colors"
aria-label={`Remove ${path}`} aria-label={`Remove ${path}` as string}
> >
<X className="h-3 w-3" /> <X className="h-3 w-3" />
</button> </button>

View file

@ -65,7 +65,7 @@ export const ScheduleSummary = (props: Props) => {
repositoryLabel: schedule.repositoryId || "No repository selected", repositoryLabel: schedule.repositoryId || "No repository selected",
retentionLabel: retentionParts.length > 0 ? retentionParts.join(" • ") : "No retention policy", retentionLabel: retentionParts.length > 0 ? retentionParts.join(" • ") : "No retention policy",
}; };
}, [schedule, schedule.volume.name]); }, [schedule]);
const handleConfirmDelete = () => { const handleConfirmDelete = () => {
setShowDeleteConfirm(false); setShowDeleteConfirm(false);

View file

@ -244,7 +244,6 @@ export default function Settings({ loaderData }: Route.ComponentProps) {
onChange={(e) => setDownloadPassword(e.target.value)} onChange={(e) => setDownloadPassword(e.target.value)}
placeholder="Enter your password" placeholder="Enter your password"
required required
autoFocus
/> />
</div> </div>
</div> </div>