Upgrade ESLint to v9 with flat config (#9680)
* Upgrade ESLint to v9 with flat config
- Upgrade eslint from v8 to v9.18.0
- Migrate from .eslintrc.js to eslint.config.mjs (flat config)
- Upgrade typescript-eslint to v8.20.0 (unified package)
- Replace eslint-plugin-import with eslint-plugin-import-x for flat config support
- Add globals package for environment globals
- Update eslint-plugin-bsky-internal with proper meta objects for ESLint v9
- Fix deprecated context.getScope() API usage
- Update bskyembed to use flat config
- Remove deprecated --ext flag from lint scripts
- Configure rules to maintain previous behavior while using new ESLint version
* Fix varsIgnorePattern to require character after underscore
Restore the original pattern `^_.+` instead of `^_` so that lingui's
`const { _ } = useLingui()` will still be flagged when unused.
* Update ESLint rule tests for flat config format
- Update RuleTester to use flat config languageOptions instead of
eslintrc parser format
- Remove duplicate test case that ESLint v9 now detects
- Add Jest globals for test files
* update eslint package versions
* lint android a11y
* enable typechecked rules, switch them to warn
* fix yarn lock ci
* Fix CI failure
* Remove unused globals?
* Organize a bit, add quiet to main lint command
* Allow ternary
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
@@ -215,18 +215,18 @@ export function Controls({
|
||||
|
||||
const seekLeft = useCallback(() => {
|
||||
if (!videoRef.current) return
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
|
||||
const currentTime = videoRef.current.currentTime
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
|
||||
const duration = videoRef.current.duration || 0
|
||||
onSeek(clamp(currentTime - 5, 0, duration))
|
||||
}, [onSeek, videoRef])
|
||||
|
||||
const seekRight = useCallback(() => {
|
||||
if (!videoRef.current) return
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
|
||||
const currentTime = videoRef.current.currentTime
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
|
||||
const duration = videoRef.current.duration || 0
|
||||
onSeek(clamp(currentTime + 5, 0, duration))
|
||||
}, [onSeek, videoRef])
|
||||
|
||||
@@ -35,6 +35,7 @@ export function Text({
|
||||
if (__DEV__) {
|
||||
if (!emoji && childHasEmoji(children)) {
|
||||
logger.warn(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-base-to-string
|
||||
`Text: emoji detected but emoji not enabled: "${children}"\n\nPlease add <Text emoji />'`,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ function DialogInner({
|
||||
} = useRemoveLiveStatusMutation()
|
||||
|
||||
const {minutesUntilExpiry, expiryDateTime} = useMemo(() => {
|
||||
tick!
|
||||
void tick
|
||||
|
||||
const expiry = new Date(status.expiresAt ?? new Date())
|
||||
return {
|
||||
|
||||
@@ -13,7 +13,11 @@ import {Admonition} from '#/components/Admonition'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import * as TextField from '#/components/forms/TextField'
|
||||
import {getLiveServiceNames} from '#/components/live/utils'
|
||||
import {
|
||||
displayDuration,
|
||||
getLiveServiceNames,
|
||||
useDebouncedValue,
|
||||
} from '#/components/live/utils'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import * as Select from '#/components/Select'
|
||||
@@ -21,7 +25,6 @@ import {Text} from '#/components/Typography'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
import {LinkPreview} from './LinkPreview'
|
||||
import {useLiveLinkMetaQuery, useUpsertLiveStatusMutation} from './queries'
|
||||
import {displayDuration, useDebouncedValue} from './utils'
|
||||
|
||||
export function GoLiveDialog({
|
||||
control,
|
||||
@@ -57,7 +60,7 @@ function DialogInner({profile}: {profile: bsky.profile.AnyProfileView}) {
|
||||
|
||||
const time = useCallback(
|
||||
(offset: number) => {
|
||||
tick!
|
||||
void tick
|
||||
|
||||
const date = new Date()
|
||||
date.setMinutes(date.getMinutes() + offset)
|
||||
|
||||
@@ -211,8 +211,8 @@ function Inner(props: ReportDialogProps) {
|
||||
logger.metric(
|
||||
'reportDialog:success',
|
||||
{
|
||||
reason: state.selectedOption?.reason!,
|
||||
labeler: state.selectedLabeler?.creator.handle!,
|
||||
reason: state.selectedOption?.reason ?? '',
|
||||
labeler: state.selectedLabeler?.creator.handle ?? '',
|
||||
details: !!state.details,
|
||||
},
|
||||
{statsig: false},
|
||||
|
||||
@@ -9,7 +9,7 @@ import {Admonition} from '#/components/Admonition'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as toast from '#/components/Toast'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Span, Text} from '#/components/Typography'
|
||||
import {useUpdateLiveEventPreferences} from '#/features/liveEvents/preferences'
|
||||
import {
|
||||
@@ -61,14 +61,14 @@ function Inner({
|
||||
feed,
|
||||
metricContext,
|
||||
onUpdateSuccess({undoAction}) {
|
||||
toast.show(
|
||||
<toast.Outer>
|
||||
<toast.Icon />
|
||||
<toast.Text>
|
||||
Toast.show(
|
||||
<Toast.Outer>
|
||||
<Toast.Icon />
|
||||
<Toast.Text>
|
||||
<Trans>Your live event preferences have been updated.</Trans>
|
||||
</toast.Text>
|
||||
</Toast.Text>
|
||||
{undoAction && (
|
||||
<toast.Action
|
||||
<Toast.Action
|
||||
label={_(msg`Undo`)}
|
||||
onPress={() => {
|
||||
if (undoAction) {
|
||||
@@ -76,12 +76,10 @@ function Inner({
|
||||
}
|
||||
}}>
|
||||
<Trans>Undo</Trans>
|
||||
</toast.Action>
|
||||
</Toast.Action>
|
||||
)}
|
||||
</toast.Outer>,
|
||||
{
|
||||
type: 'success',
|
||||
},
|
||||
</Toast.Outer>,
|
||||
{type: 'success'},
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
@@ -128,7 +128,7 @@ export function useGeolocationServiceResponse() {
|
||||
|
||||
useEffect(() => {
|
||||
return onGeolocationServiceResponseUpdate(config => {
|
||||
setConfig(config!)
|
||||
setConfig(config)
|
||||
})
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
|
||||
const config = useLiveNowConfig()
|
||||
|
||||
return useMemo(() => {
|
||||
tick! // revalidate every minute
|
||||
void tick // revalidate every minute
|
||||
|
||||
if (shadowed && 'status' in shadowed && shadowed.status) {
|
||||
const isValid = validateStatus(shadowed.status, config)
|
||||
|
||||
@@ -67,7 +67,7 @@ export function isPlainArray(value: unknown) {
|
||||
}
|
||||
|
||||
// Copied from: https://github.com/jonschlinkert/is-plain-object
|
||||
export function isPlainObject(o: any): o is Object {
|
||||
export function isPlainObject(o: any): o is object {
|
||||
if (!hasObjectPrototype(o)) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ import {
|
||||
getInfoAsync,
|
||||
readDirectoryAsync,
|
||||
} from 'expo-file-system/legacy'
|
||||
import {type ImagePickerResult} from 'expo-image-picker'
|
||||
import ExpoImageCropTool, {
|
||||
type OpenCropperOptions,
|
||||
} from '@bsky.app/expo-image-crop-tool'
|
||||
|
||||
import {compressIfNeeded} from './manip'
|
||||
import {type PickerImage} from './picker.shared'
|
||||
import {ImagePickerResult} from 'expo-image-picker'
|
||||
|
||||
async function getFile() {
|
||||
const imagesDir = documentDirectory!
|
||||
|
||||
@@ -138,11 +138,10 @@ function ChatListItemReady({
|
||||
|
||||
const {lastMessage, lastMessageSentAt, latestReportableMessage} =
|
||||
useMemo(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
let lastMessage = _(msg`No messages yet`)
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
|
||||
let lastMessageSentAt: string | null = null
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
|
||||
let latestReportableMessage: ChatBskyConvoDefs.MessageView | undefined
|
||||
|
||||
if (ChatBskyConvoDefs.isMessageView(convo.lastMessage)) {
|
||||
|
||||
@@ -64,7 +64,6 @@ export function PostThread({uri}: {uri: string}) {
|
||||
*/
|
||||
const thread = usePostThread({anchor: uri})
|
||||
const {anchor, hasParents} = useMemo(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
let hasParents = false
|
||||
for (const item of thread.data.items) {
|
||||
if (item.type === 'threadPost' && item.depth === 0) {
|
||||
|
||||
@@ -95,7 +95,7 @@ export class Convo {
|
||||
this.convoId = params.convoId
|
||||
this.agent = params.agent
|
||||
this.events = params.events
|
||||
this.senderUserDid = params.agent.session?.did!
|
||||
this.senderUserDid = params.agent.assertDid
|
||||
|
||||
if (params.placeholderData) {
|
||||
this.setupPlaceholderData(params.placeholderData)
|
||||
|
||||
@@ -22,6 +22,8 @@ const UPDATE_EVENT = 'BSKY_UPDATE'
|
||||
let _state: Schema = defaults
|
||||
const _emitter = new EventEmitter()
|
||||
|
||||
// async, to match native implementation
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
export async function init() {
|
||||
broadcast.onmessage = onBroadcastMessage
|
||||
window.onstorage = onStorage
|
||||
@@ -37,6 +39,7 @@ export function get<K extends keyof Schema>(key: K): Schema[K] {
|
||||
}
|
||||
get satisfies PersistedApi['get']
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
export async function write<K extends keyof Schema>(
|
||||
key: K,
|
||||
value: Schema[K],
|
||||
@@ -82,6 +85,7 @@ export function onUpdate<K extends keyof Schema>(
|
||||
}
|
||||
onUpdate satisfies PersistedApi['onUpdate']
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
export async function clearStorage() {
|
||||
try {
|
||||
localStorage.removeItem(BSKY_STORAGE)
|
||||
@@ -102,6 +106,7 @@ function onStorage() {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async function onBroadcastMessage({data}: MessageEvent) {
|
||||
if (
|
||||
typeof data === 'object' &&
|
||||
|
||||
@@ -23,7 +23,7 @@ export {
|
||||
useExternalEmbedsPrefs,
|
||||
useSetExternalEmbedPref,
|
||||
} from './external-embeds-prefs'
|
||||
export * from './hidden-posts'
|
||||
export {useHiddenPosts, useHiddenPostsApi} from './hidden-posts'
|
||||
export {useLabelDefinitions} from './label-defs'
|
||||
export {useLanguagePrefs, useLanguagePrefsApi} from './languages'
|
||||
export {useSetSubtitlesEnabled, useSubtitlesEnabled} from './subtitles'
|
||||
|
||||
@@ -15,6 +15,12 @@ export type UsePreferencesQueryResponse = Omit<
|
||||
}
|
||||
|
||||
export type ThreadViewPreferences = {
|
||||
sort: 'hotness' | 'oldest' | 'newest' | 'most-likes' | 'random' | string
|
||||
sort:
|
||||
| 'hotness'
|
||||
| 'oldest'
|
||||
| 'newest'
|
||||
| 'most-likes'
|
||||
| 'random'
|
||||
| (string & {})
|
||||
lab_treeViewEnabled?: boolean
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable no-labels */
|
||||
import {AppBskyUnspeccedDefs, type ModerationOpts} from '@atproto/api'
|
||||
|
||||
import {
|
||||
|
||||
@@ -26,7 +26,7 @@ type Controls = {
|
||||
/**
|
||||
* The did of the account to populate the login form with.
|
||||
*/
|
||||
requestedAccount?: string | 'none' | 'new' | 'starterpack'
|
||||
requestedAccount?: (string & {}) | 'none' | 'new' | 'starterpack'
|
||||
}) => void
|
||||
/**
|
||||
* Clears the requested account so that next time the logged out view is
|
||||
|
||||
@@ -14,7 +14,9 @@ import {
|
||||
import {AppBskyRichtextFacet, RichText} from '@atproto/api'
|
||||
import PasteInput, {
|
||||
type PastedFile,
|
||||
type PasteInputRef, // @ts-expect-error no types when installing from github
|
||||
type PasteInputRef,
|
||||
// @ts-expect-error no types when installing from github
|
||||
// eslint-disable-next-line import-x/no-unresolved
|
||||
} from '@mattermost/react-native-paste-input'
|
||||
|
||||
import {POST_IMG_MAX} from '#/lib/constants'
|
||||
|
||||
@@ -437,7 +437,6 @@ let PostFeed = ({
|
||||
for (const page of data.pages) {
|
||||
for (const slice of page.slices) {
|
||||
const item = slice.items.find(
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
item => item.uri === slice.feedPostUri,
|
||||
)
|
||||
if (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {useState} from 'react'
|
||||
import {LogBox, Pressable, View, TextInput} from 'react-native'
|
||||
import {LogBox, Pressable, TextInput, View} from 'react-native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {BLUESKY_PROXY_HEADER} from '#/lib/constants'
|
||||
import {useSessionApi, useAgent} from '#/state/session'
|
||||
import {useAgent, useSessionApi} from '#/state/session'
|
||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||
import {useOnboardingDispatch} from '#/state/shell/onboarding'
|
||||
import {navigate} from '../../../Navigation'
|
||||
@@ -50,6 +50,8 @@ export function TestCtrls() {
|
||||
return (
|
||||
<View style={{position: 'absolute', top: 100, right: 0, zIndex: 100}}>
|
||||
<TextInput
|
||||
accessibilityLabel="Text input field"
|
||||
accessibilityHint="Enter proxy header"
|
||||
testID="e2eProxyHeaderInput"
|
||||
onChangeText={val => setProxyHeader(val as any)}
|
||||
onSubmitEditing={() => {
|
||||
|
||||
@@ -3,12 +3,10 @@ import {type AlertButton, type AlertStatic} from 'react-native'
|
||||
class WebAlert implements Pick<AlertStatic, 'alert'> {
|
||||
public alert(title: string, message?: string, buttons?: AlertButton[]): void {
|
||||
if (buttons === undefined || buttons.length === 0) {
|
||||
// eslint-disable-next-line no-alert
|
||||
window.alert([title, message].filter(Boolean).join('\n'))
|
||||
return
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-alert
|
||||
const result = window.confirm([title, message].filter(Boolean).join('\n'))
|
||||
|
||||
if (result === true) {
|
||||
|
||||
@@ -153,9 +153,9 @@ export function Button({
|
||||
async (event: GestureResponderEvent) => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
withLoading && setIsLoading(true)
|
||||
if (withLoading) setIsLoading(true)
|
||||
await onPress?.(event)
|
||||
withLoading && setIsLoading(false)
|
||||
if (withLoading) setIsLoading(false)
|
||||
},
|
||||
[onPress, withLoading],
|
||||
)
|
||||
|
||||
@@ -51,6 +51,7 @@ function Text_DEPRECATED({
|
||||
if (__DEV__) {
|
||||
if (!emoji && childHasEmoji(children)) {
|
||||
logger.warn(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-base-to-string
|
||||
`Text: emoji detected but emoji not enabled: "${children}"\n\nPlease add <Text emoji />'`,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user