From 8020555115b8cc72c9de0c8bd62297c6771ad074 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 24 May 2024 10:51:04 -0500 Subject: [PATCH 1/3] turn on a couple of type-aware lint rules --- .eslintrc.cjs | 10 ++++++++++ package.json | 6 +++--- test/e2e/images.e2e.ts | 4 ++-- test/e2e/utils.ts | 5 ++++- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index dc12fdcd89..5074ab25b2 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -6,6 +6,9 @@ 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', @@ -45,6 +48,13 @@ module.exports = { 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, ], + + // type-aware rules + // https://typescript-eslint.io/getting-started/typed-linting/ + '@typescript-eslint/await-thenable': 'error', + // '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-for-in-array': 'error', + eqeqeq: ['error', 'always', { null: 'ignore' }], 'import/no-default-export': 'error', 'import/no-unresolved': 'off', // plugin doesn't know anything diff --git a/package.json b/package.json index 4e33490f44..3748f62544 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ "test": "vitest", "e2e": "playwright test", "e2ec": "playwright test --project=chrome", - "lint": "eslint --ext .js,.ts,.tsx,.json .", - "lint-fast": "eslint --cache --fix --ext .js,.ts,.tsx,.json .", + "lint": "eslint --ext .js,.ts,.tsx app test mock-api", + "lint-fast": "npm run lint -- --cache", "fmt": "prettier --cache --write . && npm run lint -- --fix", "openapi-gen-ts": "openapi-gen-ts", "prettier": "prettier", @@ -140,6 +140,6 @@ ] }, "lint-staged": { - "*.{js,ts,tsx,json}": "eslint --cache --fix" + "*.{js,ts,tsx}": "eslint --cache --fix" } } diff --git a/test/e2e/images.e2e.ts b/test/e2e/images.e2e.ts index d1aebf32e5..a86c86640f 100644 --- a/test/e2e/images.e2e.ts +++ b/test/e2e/images.e2e.ts @@ -83,11 +83,11 @@ test('can copy an image ID to clipboard', async ({ page, browserName }) => { await page.goto('/images') await clickRowAction(page, 'ubuntu-22-04', 'Copy ID') - await expect(await clipboardText(page)).toEqual('ae46ddf5-a8d5-40fa-bcda-fcac606e3f9b') + expect(await clipboardText(page)).toEqual('ae46ddf5-a8d5-40fa-bcda-fcac606e3f9b') await page.goto('/projects/mock-project/images') await clickRowAction(page, 'image-4', 'Copy ID') - await expect(await clipboardText(page)).toEqual('d150b87d-eb20-49d2-8b56-ff5564670e8c') + expect(await clipboardText(page)).toEqual('d150b87d-eb20-49d2-8b56-ff5564670e8c') }) test('can demote an image from silo', async ({ page }) => { diff --git a/test/e2e/utils.ts b/test/e2e/utils.ts index 13f7e6beda..a1800ef735 100644 --- a/test/e2e/utils.ts +++ b/test/e2e/utils.ts @@ -13,7 +13,10 @@ import { MSW_USER_COOKIE } from '../../mock-api/msw/util' export * from '@playwright/test' -export async function forEach(loc: Locator, fn: (loc0: Locator, i: number) => void) { +export async function forEach( + loc: Locator, + fn: (loc0: Locator, i: number) => Promise +) { const count = await loc.count() for (let i = 0; i < count; i++) { await fn(loc.nth(i), i) From 586233a0533bc8f3e14d16702145166347196c66 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 24 May 2024 11:40:45 -0500 Subject: [PATCH 2/3] turn on recommended-type-checked except for all the ones I hate --- .eslintrc.cjs | 14 ++++++++++---- app/components/form/SideModalForm.tsx | 4 ++-- .../form/fields/DateTimeRangePicker.spec.tsx | 4 ++-- app/components/form/fields/ListboxField.tsx | 2 +- app/forms/image-upload.tsx | 2 +- app/forms/vpc-edit.tsx | 2 +- app/pages/SiloAccessPage.tsx | 2 +- app/pages/project/access/ProjectAccessPage.tsx | 2 +- app/pages/project/instances/actions.tsx | 2 +- .../vpcs/VpcPage/tabs/VpcFirewallRulesTab.tsx | 4 +++- app/ui/lib/Button.tsx | 2 +- app/util/file.spec.ts | 2 +- app/util/math.ts | 2 ++ mock-api/msw/handlers.ts | 4 ++-- mock-api/msw/util.ts | 9 +++++++-- 15 files changed, 36 insertions(+), 21 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 5074ab25b2..6cd782607c 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -12,6 +12,7 @@ module.exports = { }, extends: [ 'eslint:recommended', + 'plugin:@typescript-eslint/recommended-type-checked', 'plugin:@typescript-eslint/strict', 'plugin:@typescript-eslint/stylistic', 'plugin:jsx-a11y/recommended', @@ -49,11 +50,16 @@ module.exports = { { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, ], - // type-aware rules + // disabling the type-aware rules we don't like // https://typescript-eslint.io/getting-started/typed-linting/ - '@typescript-eslint/await-thenable': 'error', - // '@typescript-eslint/no-floating-promises': 'error', - '@typescript-eslint/no-for-in-array': 'error', + '@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', 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..7d8724b891 100644 --- a/app/pages/project/instances/actions.tsx +++ b/app/pages/project/instances/actions.tsx @@ -70,7 +70,7 @@ export const useMakeInstanceActions = ( onActivate() { confirmAction({ actionType: 'danger', - doAction: async () => + doAction: () => stopInstance.mutate(instanceParams, { onSuccess: () => addToast({ title: `Stopping instance '${instance.name}'` }), 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={} >