Fix ESLINT 9 configuration

This commit is contained in:
Sam Becker 2025-01-05 19:47:40 -06:00
parent 6e72c02769
commit e45c1eb8d9
34 changed files with 96 additions and 95 deletions

View File

@ -1,38 +0,0 @@
{
"extends": "next/core-web-vitals",
"plugins": ["@typescript-eslint"],
"rules": {
"@next/next/no-img-element": "off",
"@typescript-eslint/no-unused-expressions": ["warn"],
"@typescript-eslint/no-unused-vars": [
"warn", {
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}
],
"comma-dangle": [
"warn",
"always-multiline"
],
"indent": [
"warn",
2
],
"linebreak-style": [
"warn",
"unix"
],
"quotes": [
"warn",
"single"
],
"semi": [
"warn",
"always"
],
"max-len": [
"warn",
{ "code": 80 }
]
}
}

51
eslint.config.mjs Normal file
View File

@ -0,0 +1,51 @@
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import { FlatCompat } from '@eslint/eslintrc';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends('next/core-web-vitals', 'next/typescript'), {
rules: {
'@next/next/no-img-element': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'no-unused-expressions': 'warn',
'@typescript-eslint/no-unused-vars': [
'warn', {
'argsIgnorePattern': '^_',
'varsIgnorePattern': '^_',
},
],
'comma-dangle': [
'warn',
'always-multiline',
],
'indent': [
'warn',
2,
],
'linebreak-style': [
'warn',
'unix',
],
'quotes': [
'warn',
'single',
],
'semi': [
'warn',
'always',
],
'max-len': [
'warn',
{ 'code': 80 },
],
},
}];
export default eslintConfig;

View File

@ -26,8 +26,6 @@
"@types/react": "19.0.2",
"@types/react-dom": "19.0.2",
"@types/sanitize-html": "^2.13.0",
"@typescript-eslint/eslint-plugin": "^8.19.0",
"@typescript-eslint/parser": "^8.19.0",
"@upstash/ratelimit": "^2.0.5",
"@vercel/analytics": "^1.4.1",
"@vercel/blob": "^0.27.0",

6
pnpm-lock.yaml generated
View File

@ -59,12 +59,6 @@ importers:
'@types/sanitize-html':
specifier: ^2.13.0
version: 2.13.0
'@typescript-eslint/eslint-plugin':
specifier: ^8.19.0
version: 8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2))(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2)
'@typescript-eslint/parser':
specifier: ^8.19.0
version: 8.19.0(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2)
'@upstash/ratelimit':
specifier: ^2.0.5
version: 2.0.5(@upstash/redis@1.34.3)

View File

@ -58,7 +58,7 @@ export default function AdminAddAllUploads({
for await (const data of readStreamableValue(stream)) {
setButtonText(addedUploadCount.current === 0
? `Adding 1 of ${storageUrls.length}`
: `Adding ${addedUploadCount.current + 1} of ${storageUrls.length}`
: `Adding ${addedUploadCount.current + 1} of ${storageUrls.length}`,
);
setUrlAddStatuses(current => {
const update = current.map(status =>
@ -70,7 +70,7 @@ export default function AdminAddAllUploads({
statusMessage: data.statusMessage,
progress: data.progress,
}
: status
: status,
);
addedUploadCount.current = update
.filter(({ status }) => status === 'added')

View File

@ -48,7 +48,7 @@ export default function AdminOutdatedClient({
onClick={async () => {
if (window.confirm(
// eslint-disable-next-line max-len
`Are you sure you want to sync the oldest ${updateBatchSize} photos? This action cannot be undone.`
`Are you sure you want to sync the oldest ${updateBatchSize} photos? This action cannot be undone.`,
)) {
const photosToSync = photos
.slice(0, updateBatchSize)

View File

@ -9,7 +9,7 @@ import { BiTrash } from 'react-icons/bi';
export default function DeleteFormButton (
props: ComponentProps<typeof SubmitButtonWithStatus> & {
clearLocalState?: boolean
}
},
) {
const {
onFormSubmit: onFormSubmitProps,

View File

@ -9,7 +9,7 @@ import { CONFIG_CHECKLIST_STATUS } from '@/site/config';
const scanForError = (
shouldCheck: boolean,
promise: () => Promise<any>
promise: () => Promise<any>,
): Promise<string> =>
shouldCheck
? promise()

View File

@ -184,7 +184,7 @@ export default function ComponentsPage() {
{DEBUG_LINES.map((_, i) =>
<div key={i}>
Line {(i + 1).toString().padStart(2, '0')}
</div>
</div>,
)}
</div>
</DivDebugBaselineGrid>

View File

@ -28,13 +28,13 @@ export default async function PhotoEditPage({
// Only generate image thumbnails when AI generation is enabled
const imageThumbnailBase64 = AI_TEXT_GENERATION_ENABLED
? await resizeImageFromUrl(
getNextImageUrlForManipulation(photo.url, IS_PREVIEW)
getNextImageUrlForManipulation(photo.url, IS_PREVIEW),
)
: '';
const blurData = BLUR_ENABLED
? await blurImageFromUrl(
getNextImageUrlForManipulation(photo.url, IS_PREVIEW)
getNextImageUrlForManipulation(photo.url, IS_PREVIEW),
)
: '';

View File

@ -25,7 +25,7 @@ export async function GET(
CURRENT_STORAGE === 'cloudflare-r2'
? cloudflareR2PutObjectCommandForKey(key)
: awsS3PutObjectCommandForKey(key),
{ expiresIn: 3600 }
{ expiresIn: 3600 },
);
return new Response(
url,

View File

@ -58,7 +58,7 @@ export const cameraFromPhoto = (
export const formatCameraText = (
{ make: makeRaw, model: modelRaw }: Camera,
includeMake: 'always' | 'never' | 'if-not-apple' = 'if-not-apple',
removeIPhoneOnLongModels?: boolean
removeIPhoneOnLongModels?: boolean,
) => {
// Remove 'Corporation' from makes like 'Nikon Corporation'
const make = makeRaw.replace(/ Corporation/i, '');

View File

@ -70,7 +70,7 @@ export default function FieldSetWithStatus({
</span>}
{isModified && !error &&
<span className={clsx(
'text-main font-medium text-[0.9rem] -ml-1.5 translate-y-[-1px]'
'text-main font-medium text-[0.9rem] -ml-1.5 translate-y-[-1px]',
)}>
*
</span>}

View File

@ -104,7 +104,7 @@ export default function ImageInput({
// Specify wide gamut to avoid data loss while resizing
const ctx = canvas?.getContext(
'2d', { colorSpace: 'display-p3' }
'2d', { colorSpace: 'display-p3' },
);
if ((shouldResize || isPng) && canvas && ctx) {

View File

@ -47,7 +47,7 @@ import { formatCount, formatCountDescriptive } from '@/utility/string';
import CommandKItem from './CommandKItem';
import { GRID_HOMEPAGE_ENABLED } from '@/site/config';
import { DialogDescription, DialogTitle } from '@radix-ui/react-dialog';
import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
import * as VisuallyHidden from '@radix-ui/react-visually-hidden';
const DIALOG_TITLE = 'Global Command-K Menu';
const DIALOG_DESCRIPTION = 'For searching photos, views, and settings';

View File

@ -46,7 +46,7 @@ export default function MoreMenu({
)}
>
{items.map(props =>
<MoreMenuItem key={`${props.label}`} {...props} />
<MoreMenuItem key={`${props.label}`} {...props} />,
)}
</DropdownMenu.Content>
</DropdownMenu.Portal>

View File

@ -44,7 +44,7 @@ export default function LabeledIcon({
{children && type !== 'icon-only' &&
<span className={clsx(
'uppercase',
debug && 'bg-gray-700'
debug && 'bg-gray-700',
)}>
{children}
</span>}

View File

@ -80,7 +80,7 @@ export default function ImagePhotoGrid({
objectFit: 'cover',
},
}} />
</div>
</div>,
)}
</div>
);

View File

@ -27,7 +27,7 @@ export default function PhotoGridPage({
useEffect(
() => () => setSelectedPhotoIds?.(undefined),
[setSelectedPhotoIds]
[setSelectedPhotoIds],
);
return (

View File

@ -176,7 +176,7 @@ export const addAllUploadsAction = async ({
})();
if (shouldRevalidateAllKeysAndPaths) {
after(revalidateAllKeysAndPaths)
after(revalidateAllKeysAndPaths);
}
return stream.value;
@ -355,7 +355,7 @@ export const syncPhotoAction = async (photoId: string) =>
semanticDescription: aiSemanticDescription,
} = await generateAiImageQueries(
imageResizedBase64,
AI_TEXT_AUTO_GENERATED_FIELDS
AI_TEXT_AUTO_GENERATED_FIELDS,
);
const photoFormDbInsert = convertFormDataToPhotoDbInsert({

View File

@ -81,7 +81,7 @@ const getPhotosCacheKeys = (options: GetPhotosOptions = {}) => {
Object.keys(options).forEach(key => {
const tag = getPhotosCacheKeyForOption(
options,
key as keyof GetPhotosOptions
key as keyof GetPhotosOptions,
);
if (tag) { tags.push(tag); }
});
@ -181,7 +181,7 @@ export const getPhotosMostRecentUpdateCached =
export const getPhotoCached = (...args: Parameters<typeof getPhoto>) =>
unstable_cache(
getPhoto,
[KEY_PHOTOS, KEY_PHOTO]
[KEY_PHOTOS, KEY_PHOTO],
)(...args).then(photo => photo ? parseCachedPhotoDates(photo) : undefined);
export const getUniqueTagsCached =
@ -193,19 +193,19 @@ export const getUniqueTagsCached =
export const getUniqueTagsHiddenCached =
unstable_cache(
getUniqueTagsHidden,
[KEY_PHOTOS, KEY_TAGS, KEY_HIDDEN]
[KEY_PHOTOS, KEY_TAGS, KEY_HIDDEN],
);
export const getUniqueCamerasCached =
unstable_cache(
getUniqueCameras,
[KEY_PHOTOS, KEY_CAMERAS]
[KEY_PHOTOS, KEY_CAMERAS],
);
export const getUniqueLensesCached =
unstable_cache(
getUniqueLenses,
[KEY_PHOTOS, KEY_LENSES]
[KEY_PHOTOS, KEY_LENSES],
);
export const getUniqueFilmSimulationsCached =

View File

@ -21,7 +21,7 @@ export const areOptionsSensitive = (options: GetPhotosOptions) =>
export const getWheresFromOptions = (
options: GetPhotosOptions,
initialValuesIndex = 1
initialValuesIndex = 1,
) => {
const {
hidden = 'exclude',

View File

@ -80,7 +80,7 @@ const runMigration02 = () =>
// Wrapper for most queries for JIT table creation/migration running
const safelyQueryPhotos = async <T>(
callback: () => Promise<T>,
debugMessage: string
debugMessage: string,
): Promise<T> => {
let result: T;
@ -370,8 +370,6 @@ export const getPhotos = async (options: GetPhotosOptions = {}) =>
lastValuesIndex,
} = getWheresFromOptions(options);
let valuesIndex = lastValuesIndex;
if (wheres) {
sql.push(wheres);
values.push(...wheresValues);
@ -382,7 +380,7 @@ export const getPhotos = async (options: GetPhotosOptions = {}) =>
const {
limitAndOffset,
limitAndOffsetValues,
} = getLimitAndOffsetFromOptions(options, valuesIndex);
} = getLimitAndOffsetFromOptions(options, lastValuesIndex);
// LIMIT + OFFSET
sql.push(limitAndOffset);
@ -421,7 +419,7 @@ export const getPhotosNearId = async (
WHERE twi.row_number >= current.row_number - 1
LIMIT $${valuesIndex++}
`,
[...wheresValues, photoId, limit]
[...wheresValues, photoId, limit],
)
.then(({ rows }) => {
const photo = rows.find(({ id }) => id === photoId);

View File

@ -133,7 +133,7 @@ export const convertFormKeysToLabels = (keys: (keyof PhotoFormData)[]) =>
keys.map(key => FORM_METADATA()[key].label.toUpperCase());
export const getFormErrors = (
formData: Partial<PhotoFormData>
formData: Partial<PhotoFormData>,
): Partial<Record<keyof PhotoFormData, string>> =>
Object.keys(formData).reduce((acc, key) => ({
...acc,
@ -147,7 +147,7 @@ export const isFormValid = (formData: Partial<PhotoFormData>) =>
(!required || Boolean(formData[key])) &&
(!validate?.(formData[key])) &&
// eslint-disable-next-line max-len
(!validateStringMaxLength || (formData[key]?.length ?? 0) <= validateStringMaxLength)
(!validateStringMaxLength || (formData[key]?.length ?? 0) <= validateStringMaxLength),
);
export const formHasTextContent = ({
@ -302,12 +302,12 @@ export const getChangedFormFields = (
.keys(current)
.filter(key =>
(original[key as keyof PhotoFormData] ?? '') !==
(current[key as keyof PhotoFormData] ?? '')
(current[key as keyof PhotoFormData] ?? ''),
) as (keyof PhotoFormData)[];
};
export const generateTakenAtFields = (
form?: Partial<PhotoFormData>
form?: Partial<PhotoFormData>,
): { takenAt: string, takenAtNaive: string } => ({
takenAt: form?.takenAt || generateLocalPostgresString(),
takenAtNaive: form?.takenAtNaive || generateLocalNaivePostgresString(),

View File

@ -112,7 +112,7 @@ export interface PhotoSetAttributes {
export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => {
const photoDb = camelcaseKeys(
photoDbRaw as unknown as Record<string, unknown>
photoDbRaw as unknown as Record<string, unknown>,
) as unknown as PhotoDb;
return {
...photoDb,
@ -193,7 +193,7 @@ export const generateOgImageMetaForPhotos = (photos: Photo[]): Metadata => {
};
const PHOTO_ID_FORWARDING_TABLE: Record<string, string> = JSON.parse(
process.env.PHOTO_ID_FORWARDING_TABLE || '{}'
process.env.PHOTO_ID_FORWARDING_TABLE || '{}',
);
export const translatePhotoId = (id: string) =>
@ -251,7 +251,7 @@ export const descriptionForPhotoSet = (
const sortPhotosByDate = (
photos: Photo[],
order: 'ASC' | 'DESC' = 'DESC'
order: 'ASC' | 'DESC' = 'DESC',
) =>
[...photos].sort((a, b) => order === 'DESC'
? b.takenAt.getTime() - a.takenAt.getTime()

View File

@ -117,14 +117,14 @@ const generateBase64 = async (
const resizeImage = async (image: ArrayBuffer) =>
generateBase64(image, sharp => sharp
.resize(IMAGE_WIDTH_RESIZE)
.resize(IMAGE_WIDTH_RESIZE),
);
const blurImage = async (image: ArrayBuffer) =>
generateBase64(image, sharp => sharp
.resize(IMAGE_WIDTH_BLUR)
.modulate({ saturation: 1.15 })
.blur(4)
.blur(4),
);
export const resizeImageFromUrl = async (url: string) =>

View File

@ -25,7 +25,7 @@ export const convertUploadToPhoto = async ({
if (shouldStripGpsData) {
const fileWithoutGps = await removeGpsData(
fileBytes ?? await fetch(urlOrigin, { cache: 'no-store' })
.then(res => res.arrayBuffer())
.then(res => res.arrayBuffer()),
);
return putFile(fileWithoutGps, photoPath).then(async url => {
if (url && shouldDeleteOrigin) { await deleteFile(urlOrigin); }

View File

@ -1,5 +1,3 @@
/* eslint-disable max-len */
const INTRINSIC_WIDTH = 28;
const INTRINSIC_HEIGHT = 24;

View File

@ -262,7 +262,7 @@ export default function SiteChecklistClient({
)}
{' '}
and connect to project
</>
</>,
)}
{hasCloudflareR2Storage
? renderSubStatus('checked', 'Cloudflare R2: connected')

View File

@ -14,7 +14,7 @@ const getFontData = async () => {
} else {
data = await fetch(new URL(
'/public/fonts/IBMPlexMono-Medium.ttf',
import.meta.url
import.meta.url,
)).then(res => res.arrayBuffer());
}
return data;

View File

@ -7,7 +7,7 @@ export const getOffsetFromExif = (data: ExifData) =>
Object.values(data.tags as any)
.find((value: any) =>
typeof value === 'string' &&
OFFSET_REGEX.test(value)
OFFSET_REGEX.test(value),
) as string | undefined;
export const getAspectRatioFromExif = (data: ExifData): number => {
@ -32,7 +32,7 @@ export const getAspectRatioFromExif = (data: ExifData): number => {
};
export const convertApertureValueToFNumber = (
apertureValue?: string
apertureValue?: string,
): string | undefined => {
if (apertureValue) {
const aperture = parseInt(apertureValue);

View File

@ -2,7 +2,7 @@ import { useEffect } from 'react';
export default function useOnVisible(
ref: React.RefObject<HTMLElement | null>,
onVisible?: () => void
onVisible?: () => void,
) {
useEffect(() => {
if (onVisible && ref.current) {

View File

@ -9,7 +9,7 @@ const safelyGetMediaQuery = () => typeof window !== 'undefined'
const usePrefersReducedMotion = () => {
const [prefersReducedMotion, setPrefersReducedMotion] = useState<boolean>(
safelyGetMediaQuery()?.matches ?? false
safelyGetMediaQuery()?.matches ?? false,
);
useEffect(() => {

View File

@ -233,7 +233,7 @@ export const labelForFilmSimulation = (simulation: FujifilmSimulation) =>
const parseFujifilmMakerNote = (
bytes: Buffer,
valueForTagUInt: (tagId: number, value: number) => void
valueForTagUInt: (tagId: number, value: number) => void,
) => {
const tagCount = bytes.readUint16LE(BYTE_INDEX_TAG_COUNT);
for (let i = 0; i < tagCount; i++) {