Skip to content

Commit 8c30305

Browse files
authored
Disallow default exports with linter, get rid of existing ones (#2050)
disallow default exports with linter. named exports are a best practice
1 parent 53709d2 commit 8c30305

31 files changed

+932
-454
lines changed

.eslintrc.cjs

+16-1
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,16 @@ module.exports = {
1515
'plugin:react/recommended',
1616
'prettier',
1717
'plugin:react-hook-form/recommended',
18+
'plugin:import/recommended',
19+
],
20+
plugins: [
21+
'@typescript-eslint',
22+
'react-hooks',
23+
'prettier',
24+
'jsx-a11y',
25+
'react-hook-form',
26+
'import',
1827
],
19-
plugins: ['@typescript-eslint', 'react-hooks', 'prettier', 'jsx-a11y', 'react-hook-form'],
2028
settings: {
2129
react: {
2230
version: 'detect',
@@ -35,6 +43,8 @@ module.exports = {
3543
'@typescript-eslint/no-non-null-assertion': 'off',
3644
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
3745
eqeqeq: ['error', 'always', { null: 'ignore' }],
46+
'import/no-default-export': 'error',
47+
'import/no-unresolved': 'off', // plugin doesn't know anything
3848
'jsx-a11y/label-has-associated-control': [2, { controlComponents: ['button'] }],
3949
'no-param-reassign': 'error',
4050
'no-restricted-imports': [
@@ -64,6 +74,11 @@ module.exports = {
6474
},
6575
ignorePatterns: ['dist/'],
6676
overrides: [
77+
{
78+
// default export is needed in config files
79+
files: ['*.config.ts'],
80+
rules: { 'import/no-default-export': 'off' },
81+
},
6782
{
6883
files: ['*.js'],
6984
rules: {

app/components/EquivalentCliCommand.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Success12Icon } from '@oxide/design-system/icons/react'
1111

1212
import { Button } from '~/ui/lib/Button'
1313
import { Modal } from '~/ui/lib/Modal'
14-
import useTimeout from '~/ui/lib/use-timeout'
14+
import { useTimeout } from '~/ui/lib/use-timeout'
1515

1616
export function EquivalentCliCommand({ command }: { command: string }) {
1717
const [isOpen, setIsOpen] = useState(false)

app/components/RefetchIntervalPicker.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Refresh16Icon, Time16Icon } from '@oxide/design-system/icons/react'
1313

1414
import { Listbox, type ListboxItem } from '~/ui/lib/Listbox'
1515
import { SpinnerLoader } from '~/ui/lib/Spinner'
16-
import useInterval from '~/ui/lib/use-interval'
16+
import { useInterval } from '~/ui/lib/use-interval'
1717

1818
const intervalPresets = {
1919
Off: undefined,

app/components/RoundedSector.tsx

+2-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useEffect, useMemo, useState } from 'react'
99

1010
import { useReducedMotion } from '~/hooks'
1111

12-
const RoundedSector = ({
12+
export function RoundedSector({
1313
angle,
1414
size,
1515
thickness,
@@ -19,7 +19,7 @@ const RoundedSector = ({
1919
size: number
2020
thickness: number
2121
cornerRadius?: number
22-
}) => {
22+
}) {
2323
const prefersReducedMotion = useReducedMotion()
2424
const [interpolatedAngle, setInterpolatedAngle] = useState(0)
2525

@@ -290,5 +290,3 @@ const polarToCartesian = (cx: number, cy: number, radius: number, angle: number)
290290
x: cx + Math.cos((-Math.PI / 180) * angle) * radius,
291291
y: cy + Math.sin((-Math.PI / 180) * angle) * radius,
292292
})
293-
294-
export default RoundedSector

app/components/Terminal.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ interface TerminalProps {
6161
ws: WebSocket
6262
}
6363

64-
export const Terminal = ({ ws }: TerminalProps) => {
64+
// default export is most convenient for dynamic import
65+
// eslint-disable-next-line import/no-default-export
66+
export default function Terminal({ ws }: TerminalProps) {
6567
const [term, setTerm] = useState<XTerm | null>(null)
6668
const terminalRef = useRef<HTMLDivElement>(null)
6769

@@ -114,5 +116,3 @@ export const Terminal = ({ ws }: TerminalProps) => {
114116
</>
115117
)
116118
}
117-
118-
export default Terminal

app/components/TimeSeriesChart.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ function roundUpToDivBy(value: number, divisor: number) {
120120
return Math.ceil(value / divisor) * divisor
121121
}
122122

123+
// default export is most convenient for dynamic import
124+
// eslint-disable-next-line import/no-default-export
123125
export default function TimeSeriesChart({
124126
className,
125127
data: rawData,

app/components/form/fields/DisksTableField.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { useController, type Control } from 'react-hook-form'
1111
import type { DiskCreate } from '@oxide/api'
1212
import { Error16Icon } from '@oxide/design-system/icons/react'
1313

14-
import AttachDiskSideModalForm from '~/forms/disk-attach'
14+
import { AttachDiskSideModalForm } from '~/forms/disk-attach'
1515
import { CreateDiskSideModalForm } from '~/forms/disk-create'
1616
import type { InstanceCreateInput } from '~/forms/instance-create'
1717
import { Badge } from '~/ui/lib/Badge'

app/components/form/fields/NetworkInterfaceField.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type {
1515
import { Error16Icon } from '@oxide/design-system/icons/react'
1616

1717
import type { InstanceCreateInput } from '~/forms/instance-create'
18-
import CreateNetworkInterfaceForm from '~/forms/network-interface-create'
18+
import { CreateNetworkInterfaceForm } from '~/forms/network-interface-create'
1919
import { Button } from '~/ui/lib/Button'
2020
import { FieldLabel } from '~/ui/lib/FieldLabel'
2121
import * as MiniTable from '~/ui/lib/MiniTable'

app/forms/disk-attach.tsx

-2
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,3 @@ export function AttachDiskSideModalForm({
6464
</SideModalForm>
6565
)
6666
}
67-
68-
export default AttachDiskSideModalForm

app/forms/network-interface-create.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type CreateNetworkInterfaceFormProps = {
3737
* Can be used with either a `setState` or a real mutation as `onSubmit`, hence
3838
* the optional `loading` and `submitError`
3939
*/
40-
export default function CreateNetworkInterfaceForm({
40+
export function CreateNetworkInterfaceForm({
4141
onSubmit,
4242
onDismiss,
4343
loading,

app/forms/network-interface-edit.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type EditNetworkInterfaceFormProps = {
2222
onDismiss: () => void
2323
}
2424

25-
export default function EditNetworkInterfaceForm({
25+
export function EditNetworkInterfaceForm({
2626
onDismiss,
2727
editing,
2828
}: EditNetworkInterfaceFormProps) {

app/layouts/AuthLayout.tsx

+14-18
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,17 @@ import { Outlet } from 'react-router-dom'
99

1010
import { OxideLogo } from '~/components/OxideLogo'
1111

12-
const AuthLayout = () => {
13-
return (
14-
<main
15-
className="relative h-screen"
16-
style={{
17-
background:
18-
'radial-gradient(200% 100% at 50% 100%, var(--surface-default) 0%, #161B1D 100%)',
19-
}}
20-
>
21-
<OxideLogo className="absolute bottom-8 left-1/2 -translate-x-1/2" />
22-
<div className="z-10 flex h-full items-center justify-center">
23-
<Outlet />
24-
</div>
25-
</main>
26-
)
27-
}
28-
29-
export default AuthLayout
12+
export const AuthLayout = () => (
13+
<main
14+
className="relative h-screen"
15+
style={{
16+
background:
17+
'radial-gradient(200% 100% at 50% 100%, var(--surface-default) 0%, #161B1D 100%)',
18+
}}
19+
>
20+
<OxideLogo className="absolute bottom-8 left-1/2 -translate-x-1/2" />
21+
<div className="z-10 flex h-full items-center justify-center">
22+
<Outlet />
23+
</div>
24+
</main>
25+
)

app/layouts/ProjectLayout.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ ProjectLayout.loader = async ({ params }: LoaderFunctionArgs) => {
4848
return null
4949
}
5050

51-
function ProjectLayout({ overrideContentPane }: ProjectLayoutProps) {
51+
export function ProjectLayout({ overrideContentPane }: ProjectLayoutProps) {
5252
const navigate = useNavigate()
5353
// project will always be there, instance may not
5454
const projectSelector = useProjectSelector()
@@ -123,5 +123,3 @@ function ProjectLayout({ overrideContentPane }: ProjectLayoutProps) {
123123
</PageContainer>
124124
)
125125
}
126-
127-
export default ProjectLayout

app/layouts/RootLayout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { useTitle } from '~/hooks/use-title'
1616
* Root layout that applies to the entire app. Modify sparingly. It's rare for
1717
* anything to actually belong here.
1818
*/
19-
export default function RootLayout() {
19+
export function RootLayout() {
2020
const title = useTitle()
2121

2222
return (

app/layouts/SettingsLayout.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { pb } from '~/util/path-builder'
1919
import { DocsLinkItem, NavLinkItem, Sidebar } from '../components/Sidebar'
2020
import { ContentPane, PageContainer } from './helpers'
2121

22-
const SettingsLayout = () => {
22+
export function SettingsLayout() {
2323
const navigate = useNavigate()
2424
const { pathname } = useLocation()
2525

@@ -67,5 +67,3 @@ const SettingsLayout = () => {
6767
</PageContainer>
6868
)
6969
}
70-
71-
export default SettingsLayout

app/layouts/SystemLayout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ SystemLayout.loader = async () => {
5050
return null
5151
}
5252

53-
export default function SystemLayout() {
53+
export function SystemLayout() {
5454
// Only show silo picker if we are looking at a particular silo. The more
5555
// robust way of doing this would be to make a separate layout for the
5656
// silo-specific routes in the route config, but it's overkill considering

app/pages/DeviceAuthSuccessPage.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { Success12Icon } from '@oxide/design-system/icons/react'
1010
/**
1111
* Device authorization success page
1212
*/
13-
export default function DeviceAuthSuccessPage() {
13+
export function DeviceAuthSuccessPage() {
1414
return (
1515
<div className="flex w-full max-w-[470px] flex-col items-center rounded-lg border p-9 text-center !bg-raise border-secondary elevation-3">
1616
<div className="my-2 flex h-12 w-12 items-center justify-center">

app/pages/DeviceAuthVerifyPage.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function addDashes(dashAfterIdxs: number[], code: string) {
3232
/**
3333
* Device authorization verification page
3434
*/
35-
export default function DeviceAuthVerifyPage() {
35+
export function DeviceAuthVerifyPage() {
3636
const navigate = useNavigate()
3737
const confirmPost = useApiMutation('deviceAuthConfirm', {
3838
onSuccess: () => {

app/pages/ProjectsPage.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ ProjectsPage.loader = async () => {
4545
return null
4646
}
4747

48-
export default function ProjectsPage() {
48+
export function ProjectsPage() {
4949
const navigate = useNavigate()
5050

5151
const queryClient = useApiQueryClient()

app/pages/project/access/ProjectAccessPage.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8-
import '@tanstack/react-table'
98

109
import { createColumnHelper, getCoreRowModel, useReactTable } from '@tanstack/react-table'
1110
import { useMemo, useState } from 'react'

app/pages/project/instances/instance/tabs/NetworkingTab.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import {
1919
} from '@oxide/api'
2020
import { Networking24Icon } from '@oxide/design-system/icons/react'
2121

22-
import CreateNetworkInterfaceForm from '~/forms/network-interface-create'
23-
import EditNetworkInterfaceForm from '~/forms/network-interface-edit'
22+
import { CreateNetworkInterfaceForm } from '~/forms/network-interface-create'
23+
import { EditNetworkInterfaceForm } from '~/forms/network-interface-edit'
2424
import {
2525
getInstanceSelector,
2626
useInstanceSelector,

app/pages/project/instances/instance/tabs/StorageTab.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
import { Storage24Icon } from '@oxide/design-system/icons/react'
2222

2323
import { DiskStatusBadge } from '~/components/StatusBadge'
24-
import AttachDiskSideModalForm from '~/forms/disk-attach'
24+
import { AttachDiskSideModalForm } from '~/forms/disk-attach'
2525
import { CreateDiskSideModalForm } from '~/forms/disk-create'
2626
import { getInstanceSelector, useInstanceSelector } from '~/hooks'
2727
import { addToast } from '~/stores/toast'

app/pages/system/silos/SilosPage.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ SilosPage.loader = async () => {
4646
return null
4747
}
4848

49-
export default function SilosPage() {
49+
export function SilosPage() {
5050
const navigate = useNavigate()
5151

5252
const { Table, Column } = useQueryTable('siloList', {})

app/routes.tsx

+9-9
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,16 @@ import { CreateVpcSideModalForm } from './forms/vpc-create'
3333
import { EditVpcSideModalForm } from './forms/vpc-edit'
3434
import type { CrumbFunc } from './hooks/use-title'
3535
import { AuthenticatedLayout } from './layouts/AuthenticatedLayout'
36-
import AuthLayout from './layouts/AuthLayout'
36+
import { AuthLayout } from './layouts/AuthLayout'
3737
import { SerialConsoleContentPane } from './layouts/helpers'
3838
import { LoginLayout } from './layouts/LoginLayout'
39-
import ProjectLayout from './layouts/ProjectLayout'
40-
import RootLayout from './layouts/RootLayout'
41-
import SettingsLayout from './layouts/SettingsLayout'
39+
import { ProjectLayout } from './layouts/ProjectLayout'
40+
import { RootLayout } from './layouts/RootLayout'
41+
import { SettingsLayout } from './layouts/SettingsLayout'
4242
import { SiloLayout } from './layouts/SiloLayout'
43-
import SystemLayout from './layouts/SystemLayout'
44-
import DeviceAuthSuccessPage from './pages/DeviceAuthSuccessPage'
45-
import DeviceAuthVerifyPage from './pages/DeviceAuthVerifyPage'
43+
import { SystemLayout } from './layouts/SystemLayout'
44+
import { DeviceAuthSuccessPage } from './pages/DeviceAuthSuccessPage'
45+
import { DeviceAuthVerifyPage } from './pages/DeviceAuthVerifyPage'
4646
import { LoginPage } from './pages/LoginPage'
4747
import { LoginPageSaml } from './pages/LoginPageSaml'
4848
import { instanceLookupLoader } from './pages/lookups'
@@ -60,7 +60,7 @@ import { InstancesPage } from './pages/project/instances/InstancesPage'
6060
import { SnapshotsPage } from './pages/project/snapshots/SnapshotsPage'
6161
import { VpcPage } from './pages/project/vpcs/VpcPage/VpcPage'
6262
import { VpcsPage } from './pages/project/vpcs/VpcsPage'
63-
import ProjectsPage from './pages/ProjectsPage'
63+
import { ProjectsPage } from './pages/ProjectsPage'
6464
import { ProfilePage } from './pages/settings/ProfilePage'
6565
import { SSHKeysPage } from './pages/settings/SSHKeysPage'
6666
import { SiloAccessPage } from './pages/SiloAccessPage'
@@ -75,7 +75,7 @@ import { IpPoolsTab } from './pages/system/networking/IpPoolsTab'
7575
import { NetworkingPage } from './pages/system/networking/NetworkingPage'
7676
import { SiloImagesPage } from './pages/system/SiloImagesPage'
7777
import { SiloPage } from './pages/system/silos/SiloPage'
78-
import SilosPage from './pages/system/silos/SilosPage'
78+
import { SilosPage } from './pages/system/silos/SilosPage'
7979
import { SystemUtilizationPage } from './pages/system/UtilizationPage'
8080
import { pb } from './util/path-builder'
8181

app/ui/lib/CopyToClipboard.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { useState } from 'react'
1212

1313
import { Copy12Icon, Success12Icon } from '@oxide/design-system/icons/react'
1414

15-
import useTimeout from './use-timeout'
15+
import { useTimeout } from './use-timeout'
1616

1717
export const CopyToClipboard = ({
1818
ariaLabel = 'Click to copy this text',

app/ui/lib/TimeoutIndicator.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { animated, Globals, useTransition } from '@react-spring/web'
99
import cn from 'classnames'
1010

11-
import useTimeout from './use-timeout'
11+
import { useTimeout } from './use-timeout'
1212

1313
export interface TimeoutIndicatorProps {
1414
timeout: number

app/ui/lib/use-interval.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ interface UseIntervalProps {
2121
* force a render, which cleans up the currently set interval and possibly sets
2222
* a new one.
2323
*/
24-
export default function useInterval({ fn, delay, key }: UseIntervalProps) {
24+
export function useInterval({ fn, delay, key }: UseIntervalProps) {
2525
const callbackRef = useRef<() => void>()
2626

2727
useEffect(() => {

app/ui/lib/use-timeout.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { useEffect, useRef } from 'react'
99

1010
// use null delay to prevent the timeout from firing
11-
export default function useTimeout(callback: () => void, delay: number | null) {
11+
export function useTimeout(callback: () => void, delay: number | null) {
1212
const callbackRef = useRef<() => void>()
1313

1414
useEffect(() => {

0 commit comments

Comments
 (0)