diff --git a/.eslintrc.cjs b/.eslintrc.cjs index dc12fdcd89..6cd782607c 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -6,9 +6,13 @@ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { warnOnUnsupportedTypeScriptVersion: false, + // this config is needed for type aware lint rules + project: true, + tsconfigRootDir: __dirname, }, extends: [ 'eslint:recommended', + 'plugin:@typescript-eslint/recommended-type-checked', 'plugin:@typescript-eslint/strict', 'plugin:@typescript-eslint/stylistic', 'plugin:jsx-a11y/recommended', @@ -45,6 +49,18 @@ module.exports = { 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, ], + + // disabling the type-aware rules we don't like + // https://typescript-eslint.io/getting-started/typed-linting/ + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/unbound-method': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + eqeqeq: ['error', 'always', { null: 'ignore' }], 'import/no-default-export': 'error', 'import/no-unresolved': 'off', // plugin doesn't know anything diff --git a/app/components/form/SideModalForm.tsx b/app/components/form/SideModalForm.tsx index e066f1bd3f..e0e02b7cbe 100644 --- a/app/components/form/SideModalForm.tsx +++ b/app/components/form/SideModalForm.tsx @@ -7,7 +7,7 @@ */ import { useEffect, useId, type ReactNode } from 'react' import type { FieldValues, UseFormReturn } from 'react-hook-form' -import { useNavigationType } from 'react-router-dom' +import { NavigationType, useNavigationType } from 'react-router-dom' import type { ApiError } from '@oxide/api' @@ -57,7 +57,7 @@ type SideModalFormProps = { * any way to distinguish between fresh pageload and back/forward. */ export function useShouldAnimateModal() { - return useNavigationType() === 'PUSH' + return useNavigationType() === NavigationType.Push } export function SideModalForm({ diff --git a/app/components/form/fields/DateTimeRangePicker.spec.tsx b/app/components/form/fields/DateTimeRangePicker.spec.tsx index 0d9e37ec27..12c7274754 100644 --- a/app/components/form/fields/DateTimeRangePicker.spec.tsx +++ b/app/components/form/fields/DateTimeRangePicker.spec.tsx @@ -97,7 +97,7 @@ describe.skip('custom mode', () => { expect(screen.getByRole('button', { name: 'Load' })).toHaveClass('visually-disabled') }) - it('clicking load after changing date changes range', async () => { + it('clicking load after changing date changes range', () => { const { setRange } = renderLastDay() // expect(screen.getByLabelText('Start time')).toHaveValue(dateForInput(subDays(now, 1))) @@ -125,7 +125,7 @@ describe.skip('custom mode', () => { }) }) - it('clicking reset after changing inputs resets inputs', async () => { + it('clicking reset after changing inputs resets inputs', () => { const { setRange } = renderLastDay() // expect(screen.getByLabelText('Start time')).toHaveValue(dateForInput(subDays(now, 1))) diff --git a/app/components/form/fields/ListboxField.tsx b/app/components/form/fields/ListboxField.tsx index 09241ffc09..20f3da2b5d 100644 --- a/app/components/form/fields/ListboxField.tsx +++ b/app/components/form/fields/ListboxField.tsx @@ -27,7 +27,7 @@ export type ListboxFieldProps< className?: string label?: string required?: boolean - description?: string | React.ReactNode | React.ReactNode + description?: string | React.ReactNode tooltipText?: string control: Control disabled?: boolean diff --git a/app/forms/image-upload.tsx b/app/forms/image-upload.tsx index b3be957b9c..6d7f5edaf8 100644 --- a/app/forms/image-upload.tsx +++ b/app/forms/image-upload.tsx @@ -273,7 +273,7 @@ export function CreateImageSideModalForm() { // coordinating when to cleanup, we make cleanup idempotent by having it check // whether it has already been run, or more concretely before each action, // check whether it needs to be done - async function closeModal() { + function closeModal() { if (allDone) { backToImages() return diff --git a/app/forms/vpc-edit.tsx b/app/forms/vpc-edit.tsx index 87859b5521..b50a73ab40 100644 --- a/app/forms/vpc-edit.tsx +++ b/app/forms/vpc-edit.tsx @@ -40,7 +40,7 @@ export function EditVpcSideModalForm() { const onDismiss = () => navigate(pb.vpcs({ project })) const editVpc = useApiMutation('vpcUpdate', { - async onSuccess(vpc) { + onSuccess(vpc) { queryClient.invalidateQueries('vpcList') queryClient.setQueryData( 'vpcView', diff --git a/app/pages/SiloAccessPage.tsx b/app/pages/SiloAccessPage.tsx index c83cf198d4..51fc141373 100644 --- a/app/pages/SiloAccessPage.tsx +++ b/app/pages/SiloAccessPage.tsx @@ -139,7 +139,7 @@ export function SiloAccessPage() { doDelete: () => updatePolicy.mutateAsync({ // we know policy is there, otherwise there's no row to display - body: deleteRole(row.id, siloPolicy!), + body: deleteRole(row.id, siloPolicy), }), label: ( diff --git a/app/pages/project/access/ProjectAccessPage.tsx b/app/pages/project/access/ProjectAccessPage.tsx index 1e20984980..b93de0fe2b 100644 --- a/app/pages/project/access/ProjectAccessPage.tsx +++ b/app/pages/project/access/ProjectAccessPage.tsx @@ -169,7 +169,7 @@ export function ProjectAccessPage() { updatePolicy.mutateAsync({ path: { project }, // we know policy is there, otherwise there's no row to display - body: deleteRole(row.id, projectPolicy!), + body: deleteRole(row.id, projectPolicy), }), // TODO: explain that this will not affect the role inherited from // the silo or roles inherited from group membership. Ideally we'd diff --git a/app/pages/project/instances/actions.tsx b/app/pages/project/instances/actions.tsx index 5a1650aa69..f0c91316fd 100644 --- a/app/pages/project/instances/actions.tsx +++ b/app/pages/project/instances/actions.tsx @@ -70,16 +70,10 @@ export const useMakeInstanceActions = ( onActivate() { confirmAction({ actionType: 'danger', - doAction: async () => - stopInstance.mutate(instanceParams, { + doAction: () => + stopInstance.mutateAsync(instanceParams, { onSuccess: () => addToast({ title: `Stopping instance '${instance.name}'` }), - onError: (error) => - addToast({ - variant: 'error', - title: `Error stopping instance '${instance.name}'`, - content: error.message, - }), }), modalTitle: 'Confirm stop instance', modalContent: ( @@ -89,7 +83,7 @@ export const useMakeInstanceActions = ( freed.

), - errorTitle: `Could not stop ${instance.name}`, + errorTitle: `Error stopping ${instance.name}`, }) }, disabled: !instanceCan.stop(instance) && ( diff --git a/app/pages/project/vpcs/VpcPage/tabs/VpcFirewallRulesTab.tsx b/app/pages/project/vpcs/VpcPage/tabs/VpcFirewallRulesTab.tsx index c70a6b0e7d..854cf36f57 100644 --- a/app/pages/project/vpcs/VpcPage/tabs/VpcFirewallRulesTab.tsx +++ b/app/pages/project/vpcs/VpcPage/tabs/VpcFirewallRulesTab.tsx @@ -72,7 +72,9 @@ const staticColumns = [ cell: (info) => { const { hosts, ports, protocols } = info.getValue() const children = [ - ...(hosts || []).map((tv, i) => ), + ...(hosts || []).map((tv, i) => ( + + )), ...(protocols || []).map((p, i) => {p}), ...(ports || []).map((p, i) => ( diff --git a/app/ui/lib/Button.tsx b/app/ui/lib/Button.tsx index ae972219b0..cc43d4c99e 100644 --- a/app/ui/lib/Button.tsx +++ b/app/ui/lib/Button.tsx @@ -87,7 +87,7 @@ export const Button = forwardRef( return ( } + with={} >