Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v4] setup @typescript-eslint/no-unnecessary-condition rule and fix warnings #3747

Merged
merged 25 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/blue-crabs-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"nextra-theme-blog": patch
"nextra-theme-docs": patch
"nextra": patch
---

setup `@typescript-eslint/no-unnecessary-condition` rule and fix warnings
2 changes: 1 addition & 1 deletion docs/app/docs/built-ins/head/_slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const Slider: FC = ({
}

function hexToRgb(hex: `#${string}`): string {
const bigint = parseInt(hex.slice(1), 16)
const bigint = Number.parseInt(hex.slice(1), 16)
const r = (bigint >> 16) & 255
const g = (bigint >> 8) & 255
const b = bigint & 255
Expand Down
2 changes: 1 addition & 1 deletion docs/app/og/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ImageResponse } from 'next/og'

export const runtime = 'edge'

const font = fetch(new URL('./Inter-SemiBold.otf', import.meta.url)).then(res =>
const font = fetch(new URL('Inter-SemiBold.otf', import.meta.url)).then(res =>
res.arrayBuffer()
)

Expand Down
3 changes: 2 additions & 1 deletion docs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"name": "next"
}
],
"strictNullChecks": true
"strictNullChecks": true,
"noUncheckedIndexedAccess": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules", ".next"]
Expand Down
3 changes: 2 additions & 1 deletion examples/blog/app/posts/get-posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export async function getPosts() {
}

export async function getTags() {
const tags = (await getPosts()).flatMap(post => post.frontMatter.tags)
const posts = await getPosts()
const tags = posts.flatMap(post => post.frontMatter.tags)
return tags
}
6 changes: 4 additions & 2 deletions examples/blog/app/posts/page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ export const metadata = {
}

export default async function PostsPage() {
const allTags = (await getTags()).reduce((acc, curr) => {
const tags = await getTags()
const posts = await getPosts()
const allTags = tags.reduce((acc, curr) => {
acc[curr] ??= 0
acc[curr] += 1
return acc
Expand All @@ -26,7 +28,7 @@ export default async function PostsPage() {
</Link>
))}
</div>
{(await getPosts()).map(post => (
{posts.map(post => (
<PostCard key={post.route} post={post} />
))}
</div>
Expand Down
6 changes: 4 additions & 2 deletions examples/blog/app/tags/[tag]/page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ export async function generateStaticParams() {

export default async function TagPage(props) {
const params = await props.params
const { title } = await generateMetadata({ params })
const posts = await getPosts()
return (
<>
<h1>{(await generateMetadata({ params })).title}</h1>
{(await getPosts())
<h1>{title}</h1>
{posts
.filter(post =>
post.frontMatter.tags.includes(decodeURIComponent(params.tag))
)
Expand Down
11 changes: 2 additions & 9 deletions examples/swr-site/app/_dictionaries/get-dictionary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const dictionaries: Dictionaries = {
ru: () => import('./ru')
}

export async function getDictionary(locale: Locale): Promise<Dictionary> {
export async function getDictionary(locale: string): Promise<Dictionary> {
const { default: dictionary } = await (
dictionaries[locale] || dictionaries.en
)()
Expand All @@ -18,12 +18,5 @@ export async function getDictionary(locale: Locale): Promise<Dictionary> {
}

export function getDirection(locale: Locale): 'ltr' | 'rtl' {
switch (locale) {
case 'es':
return 'rtl'
case 'en':
case 'ru':
default:
return 'ltr'
}
return locale === 'es' ? 'rtl' : 'ltr'
}
3 changes: 2 additions & 1 deletion examples/swr-site/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"name": "next"
}
],
"strictNullChecks": true
"strictNullChecks": true,
"noUncheckedIndexedAccess": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules", ".next"]
Expand Down
1 change: 1 addition & 0 deletions packages/esbuild-react-compiler-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const reactCompilerPlugin = (
relativePath,
'was not optimized with react-compiler'
)
console.log(result)
}

resolve({ contents: result, loader })
Expand Down
3 changes: 2 additions & 1 deletion packages/esbuild-react-compiler-plugin/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"strictNullChecks": true,
"lib": ["esnext", "dom"],
"moduleResolution": "node",
"resolveJsonModule": true
"resolveJsonModule": true,
"noUncheckedIndexedAccess": true
},
"exclude": ["dist"]
}
32 changes: 18 additions & 14 deletions packages/eslint-config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ const config: Config = tseslint.config(
extends: [
js.configs.recommended,
tseslint.configs.recommended,
eslintPluginUnicorn.configs['flat/recommended'],
eslintConfigPrettier
],
plugins: {
import: eslintPluginImport,
unicorn: eslintPluginUnicorn,
sonarjs: eslintPluginSonarJs
},
rules: {
Expand All @@ -68,13 +68,8 @@ const config: Config = tseslint.config(
{ VariableDeclarator: { object: true } }
],
'import/no-duplicates': 'error',
'no-negated-condition': 'off',
'unicorn/no-negated-condition': 'error',
'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }],
'object-shorthand': ['error', 'always'],
'unicorn/prefer-regexp-test': 'error',
'unicorn/no-array-for-each': 'error',
'unicorn/prefer-string-replace-all': 'error',
'@typescript-eslint/prefer-for-of': 'error',
quotes: ['error', 'single', { avoidEscape: true }], // Matches Prettier, but also replaces backticks
'@typescript-eslint/no-unused-vars': [
Expand All @@ -86,21 +81,29 @@ const config: Config = tseslint.config(
],
'prefer-object-spread': 'error',
'prefer-arrow-callback': ['error', { allowNamedFunctions: true }],
'unicorn/prefer-at': 'error',
'sonarjs/no-small-switch': 'error',
'prefer-const': ['error', { destructuring: 'all' }],
'unicorn/prefer-array-index-of': 'error',
'sonarjs/no-unused-collection': 'error',
'unicorn/catch-error-name': 'error',
'unicorn/prefer-optional-catch-binding': 'error',
'unicorn/filename-case': 'error',
eqeqeq: ['error', 'always', { null: 'ignore' }],
'unicorn/prefer-node-protocol': 'error',
'unicorn/switch-case-braces': ['error', 'avoid'],
// todo: enable
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/ban-ts-comment': 'off'
'@typescript-eslint/ban-ts-comment': 'off',

'unicorn/no-hex-escape': 'off', // todo
'unicorn/escape-case': 'off', // todo
'unicorn/consistent-function-scoping': 'off', // todo
'unicorn/prefer-module': 'off',
'unicorn/no-array-reduce': 'off',
'unicorn/prefer-top-level-await': 'off', // Check if possible to refactor without breaking

'unicorn/prevent-abbreviations': 'off', // Too many cases
'unicorn/explicit-length-check': 'off', // I don't like
'unicorn/no-null': 'off', // I don't like
'unicorn/prefer-global-this': 'off', // Bundlers are smarter with window
'unicorn/prefer-optional-catch-binding': 'off' // catch by @typescript-eslint/no-unused-vars
}
},
// Rules for React files
Expand Down Expand Up @@ -174,7 +177,8 @@ const config: Config = tseslint.config(
'@typescript-eslint/prefer-destructuring': [
'error',
{ VariableDeclarator: { object: true } }
]
],
'@typescript-eslint/no-unnecessary-condition': 'error'
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-config/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"strictNullChecks": true,
"lib": ["esnext", "dom"],
"moduleResolution": "node",
"resolveJsonModule": true
"noUncheckedIndexedAccess": true
},
"exclude": ["dist"]
}
9 changes: 6 additions & 3 deletions packages/nextra-theme-blog/src/components/meta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ export const Meta: FC<BlogMetadata & { children: ReactNode }> = ({

{children}

{(author || date) && (readingTime || tags?.length) && (
<span className="x:px-1">•</span>
)}
{
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- fixme
(author || date) && (readingTime || tags?.length) && (
<span className="x:px-1">•</span>
)
}
{readingTimeText || tagsEl}
</div>
{readingTime && (
Expand Down
7 changes: 5 additions & 2 deletions packages/nextra-theme-blog/src/mdx-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,11 @@ export const useMDXComponents = ({
<Meta {...(metadata as BlogMetadata)}>
{dateObj && (
<time dateTime={dateObj.toISOString()}>
{(DateFormatter && <DateFormatter date={dateObj} />) ||
dateObj.toLocaleDateString()}
{DateFormatter ? (
<DateFormatter date={dateObj} />
) : (
dateObj.toLocaleDateString()
)}
</time>
)}
</Meta>
Expand Down
3 changes: 2 additions & 1 deletion packages/nextra-theme-blog/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"strictNullChecks": true,
"jsx": "react-jsx",
"moduleResolution": "bundler",
"types": ["vitest/globals"]
"types": ["vitest/globals"],
"noUncheckedIndexedAccess": true
},
"exclude": ["dist"]
}
4 changes: 2 additions & 2 deletions packages/nextra-theme-docs/src/components/locale-switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { GlobeIcon } from 'nextra/icons'
import type { FC } from 'react'
import { useThemeConfig } from '../stores'

const ONE_YEAR = 365 * 24 * 60 * 60 * 1_000
const ONE_YEAR = 365 * 24 * 60 * 60 * 1000

interface LocaleSwitchProps {
lite?: boolean
Expand All @@ -29,7 +29,7 @@ export const LocaleSwitch: FC<LocaleSwitchProps> = ({ lite, className }) => {
document.cookie = `NEXT_LOCALE=${lang}; expires=${date.toUTCString()}; path=/`
location.href = addBasePath(pathname.replace(`/${locale}`, `/${lang}`))
}}
value={locale}
value={locale!}
selectedOption={
<span className="x:flex x:items-center x:gap-2">
<GlobeIcon height="12" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const NavbarMenu: FC<{
anchor={{ to: 'top end', gap: 10, padding: 16 }}
>
{Object.entries(
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- fixme
(menu.items as Record<string, { title: string; href?: string }>) || {}
).map(([key, item]) => (
<_MenuItem
Expand Down
49 changes: 25 additions & 24 deletions packages/nextra-theme-docs/src/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ type FolderProps = {

const Folder: FC<FolderProps> = ({ item, anchors, onFocus, level }) => {
const routeOriginal = useFSRoute()
const [route] = routeOriginal.split('#', 1)
const route = routeOriginal.split('#', 1)[0]!
const hasRoute = !!item.route // for item.type === 'menu' will be ''
const active = hasRoute && [route, route + '/'].includes(item.route + '/')
const activeRouteInside =
Expand All @@ -99,19 +99,20 @@ const Folder: FC<FolderProps> = ({ item, anchors, onFocus, level }) => {

const [, rerender] = useState<object>()

const handleClick: MouseEventHandler = useCallback(event => {
const el = event.currentTarget
const isClickOnIcon =
el /* will be always <a> or <button> */ !==
event.target /* can be <svg> or <path> */
if (isClickOnIcon) {
event.preventDefault()
}
const isOpen = el.parentElement!.classList.contains('open')
const route = el.getAttribute('href') || el.getAttribute('data-href') || ''
TreeState[route] = !isOpen
rerender({})
}, [])
const handleClick: MouseEventHandler<HTMLAnchorElement | HTMLButtonElement> =
useCallback(event => {
const el = event.currentTarget
const isClickOnIcon =
el /* will be always <a> or <button> */ !==
event.target /* can be <svg> or <path> */
if (isClickOnIcon) {
event.preventDefault()
}
const isOpen = el.parentElement!.classList.contains('open')
const route = el.getAttribute('href') || el.dataset.href || ''
TreeState[route] = !isOpen
rerender({})
}, [])

useEffect(() => {
function updateTreeState() {
Expand Down Expand Up @@ -140,12 +141,14 @@ const Folder: FC<FolderProps> = ({ item, anchors, onFocus, level }) => {
const routes = Object.fromEntries(
(menu.children || []).map(route => [route.name, route])
)
item.children = Object.entries(menu.items || {}).map(([key, item]) => {
return {
...(routes[key] || { name: key /* for React key prop */ }),
...(item as object)
}
})
// @ts-expect-error
item.children = Object.entries(menu.items || {}) // eslint-disable-line @typescript-eslint/no-unnecessary-condition -- fixme
.map(([key, item]) => {
return {
...(routes[key] || { name: key /* for React key prop */ }),
...(item as object)
}
})
}

const isLink = 'frontMatter' in item
Expand Down Expand Up @@ -265,11 +268,9 @@ interface MenuProps {
level: number
}

const handleFocus: FocusEventHandler = event => {
const handleFocus: FocusEventHandler<HTMLAnchorElement> = event => {
const route =
event.target.getAttribute('href') ||
event.target.getAttribute('data-href') ||
''
event.target.getAttribute('href') || event.target.dataset.href || ''
setFocusedRoute(route)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ export const ClientWrapper: MDXWrapper = ({ toc, children, metadata }) => {
} = useConfig().normalizePagesResult
const themeConfig = useThemeConfig()

const date =
themeContext.timestamp && themeConfig.lastUpdated && metadata.timestamp
const date = themeContext.timestamp && metadata.timestamp

// We can't update store in server component so doing it in client component
useEffect(() => {
Expand Down
3 changes: 2 additions & 1 deletion packages/nextra-theme-docs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"jsx": "react-jsx",
"moduleResolution": "bundler",
"lib": ["ESNext", "DOM"],
"types": ["vitest/globals"]
"types": ["vitest/globals"],
"noUncheckedIndexedAccess": true
},
"exclude": ["dist"]
}
2 changes: 1 addition & 1 deletion packages/nextra/loader.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* @param {string} code
* @return {Promise<void>}
*/
module.exports = async function (code) {
module.exports = async function loader(code) {
const callback = this.async()

try {
Expand Down
Loading