From fbeef1538c5310c815bed8802311406bfef4d0d3 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Fri, 29 Nov 2024 14:21:47 +0700 Subject: [PATCH] [v4] setup `@typescript-eslint/no-unnecessary-condition` rule and fix warnings (#3747) * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * fix * aa * more * more * more * fix typecheck --- .changeset/blue-crabs-deny.md | 7 +++ docs/app/docs/built-ins/head/_slider.tsx | 2 +- docs/app/og/route.tsx | 2 +- docs/tsconfig.json | 3 +- examples/blog/app/posts/get-posts.js | 3 +- examples/blog/app/posts/page.jsx | 6 ++- examples/blog/app/tags/[tag]/page.jsx | 6 ++- .../app/_dictionaries/get-dictionary.ts | 11 +---- examples/swr-site/tsconfig.json | 3 +- .../src/index.ts | 1 + .../tsconfig.json | 3 +- packages/eslint-config/src/index.ts | 32 ++++++------ packages/eslint-config/tsconfig.json | 2 +- .../nextra-theme-blog/src/components/meta.tsx | 9 ++-- .../nextra-theme-blog/src/mdx-components.tsx | 7 ++- packages/nextra-theme-blog/tsconfig.json | 3 +- .../src/components/locale-switch.tsx | 4 +- .../src/components/navbar/index.client.tsx | 1 + .../src/components/sidebar.tsx | 49 ++++++++++--------- .../src/mdx-components/wrapper.client.tsx | 3 +- packages/nextra-theme-docs/tsconfig.json | 3 +- packages/nextra/loader.cjs | 2 +- .../nextra/src/client/components/head.tsx | 8 +-- .../client/components/popup/index.client.tsx | 1 + .../nextra/src/client/components/search.tsx | 7 +-- .../src/client/hocs/with-github-alert.tsx | 6 ++- .../mdx-components/pre/copy-to-clipboard.tsx | 4 -- packages/nextra/src/client/normalize-pages.ts | 9 ++-- packages/nextra/src/env.d.ts | 20 ++++---- .../folder-without-markdown-files/test.ts | 1 + .../nextra/src/server/__tests__/latex.test.ts | 2 +- .../src/server/__tests__/normalize.test.ts | 6 +-- .../nextra/src/server/compile-metadata.ts | 2 +- packages/nextra/src/server/compile.ts | 8 +-- .../src/server/fetch-filepaths-from-github.ts | 1 + packages/nextra/src/server/index.ts | 10 ++-- packages/nextra/src/server/loader.ts | 2 +- packages/nextra/src/server/locales.ts | 2 +- .../page-map/merge-meta-with-page-map.ts | 2 +- .../nextra/src/server/page-map/normalize.ts | 34 ++++++------- .../nextra/src/server/page-map/placeholder.ts | 1 + .../nextra/src/server/page-map/to-page-map.ts | 3 ++ .../src/server/recma-plugins/recma-rewrite.ts | 4 +- .../rehype-better-react-mathjax.ts | 7 +-- .../rehype-extract-toc-content.ts | 2 +- .../src/server/rehype-plugins/rehype.ts | 4 +- .../server/remark-plugins/remark-mdx-title.ts | 2 +- packages/nextra/src/server/twoslash.ts | 8 +-- packages/nextra/tsconfig.json | 3 +- packages/nextra/tsup.config.ts | 2 +- 50 files changed, 181 insertions(+), 142 deletions(-) create mode 100644 .changeset/blue-crabs-deny.md diff --git a/.changeset/blue-crabs-deny.md b/.changeset/blue-crabs-deny.md new file mode 100644 index 0000000000..dd79c8bc16 --- /dev/null +++ b/.changeset/blue-crabs-deny.md @@ -0,0 +1,7 @@ +--- +"nextra-theme-blog": patch +"nextra-theme-docs": patch +"nextra": patch +--- + +setup `@typescript-eslint/no-unnecessary-condition` rule and fix warnings diff --git a/docs/app/docs/built-ins/head/_slider.tsx b/docs/app/docs/built-ins/head/_slider.tsx index 271cdf9625..f2fad2811a 100644 --- a/docs/app/docs/built-ins/head/_slider.tsx +++ b/docs/app/docs/built-ins/head/_slider.tsx @@ -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 diff --git a/docs/app/og/route.tsx b/docs/app/og/route.tsx index 37a818cc13..04795d22f5 100644 --- a/docs/app/og/route.tsx +++ b/docs/app/og/route.tsx @@ -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() ) diff --git a/docs/tsconfig.json b/docs/tsconfig.json index e0c194d17a..312a3448e2 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -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"] diff --git a/examples/blog/app/posts/get-posts.js b/examples/blog/app/posts/get-posts.js index f5bd0d4252..f33e6f37cb 100644 --- a/examples/blog/app/posts/get-posts.js +++ b/examples/blog/app/posts/get-posts.js @@ -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 } diff --git a/examples/blog/app/posts/page.jsx b/examples/blog/app/posts/page.jsx index 054e9d8dd2..47b3d2c970 100644 --- a/examples/blog/app/posts/page.jsx +++ b/examples/blog/app/posts/page.jsx @@ -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 @@ -26,7 +28,7 @@ export default async function PostsPage() { ))} - {(await getPosts()).map(post => ( + {posts.map(post => ( ))} diff --git a/examples/blog/app/tags/[tag]/page.jsx b/examples/blog/app/tags/[tag]/page.jsx index 39eb22f0e2..fce9f1eb84 100644 --- a/examples/blog/app/tags/[tag]/page.jsx +++ b/examples/blog/app/tags/[tag]/page.jsx @@ -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 ( <> -

{(await generateMetadata({ params })).title}

- {(await getPosts()) +

{title}

+ {posts .filter(post => post.frontMatter.tags.includes(decodeURIComponent(params.tag)) ) diff --git a/examples/swr-site/app/_dictionaries/get-dictionary.ts b/examples/swr-site/app/_dictionaries/get-dictionary.ts index 7e5a08cd3f..86c4b83859 100644 --- a/examples/swr-site/app/_dictionaries/get-dictionary.ts +++ b/examples/swr-site/app/_dictionaries/get-dictionary.ts @@ -9,7 +9,7 @@ const dictionaries: Dictionaries = { ru: () => import('./ru') } -export async function getDictionary(locale: Locale): Promise { +export async function getDictionary(locale: string): Promise { const { default: dictionary } = await ( dictionaries[locale] || dictionaries.en )() @@ -18,12 +18,5 @@ export async function getDictionary(locale: Locale): Promise { } 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' } diff --git a/examples/swr-site/tsconfig.json b/examples/swr-site/tsconfig.json index c34283ef38..b17c6c3395 100644 --- a/examples/swr-site/tsconfig.json +++ b/examples/swr-site/tsconfig.json @@ -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"] diff --git a/packages/esbuild-react-compiler-plugin/src/index.ts b/packages/esbuild-react-compiler-plugin/src/index.ts index f0349fa787..f62dde6c07 100644 --- a/packages/esbuild-react-compiler-plugin/src/index.ts +++ b/packages/esbuild-react-compiler-plugin/src/index.ts @@ -45,6 +45,7 @@ export const reactCompilerPlugin = ( relativePath, 'was not optimized with react-compiler' ) + console.log(result) } resolve({ contents: result, loader }) diff --git a/packages/esbuild-react-compiler-plugin/tsconfig.json b/packages/esbuild-react-compiler-plugin/tsconfig.json index 61aa255661..601682dd6e 100644 --- a/packages/esbuild-react-compiler-plugin/tsconfig.json +++ b/packages/esbuild-react-compiler-plugin/tsconfig.json @@ -10,7 +10,8 @@ "strictNullChecks": true, "lib": ["esnext", "dom"], "moduleResolution": "node", - "resolveJsonModule": true + "resolveJsonModule": true, + "noUncheckedIndexedAccess": true }, "exclude": ["dist"] } diff --git a/packages/eslint-config/src/index.ts b/packages/eslint-config/src/index.ts index 34dd68d9a1..046f34ab44 100644 --- a/packages/eslint-config/src/index.ts +++ b/packages/eslint-config/src/index.ts @@ -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: { @@ -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': [ @@ -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 @@ -174,7 +177,8 @@ const config: Config = tseslint.config( '@typescript-eslint/prefer-destructuring': [ 'error', { VariableDeclarator: { object: true } } - ] + ], + '@typescript-eslint/no-unnecessary-condition': 'error' } }, { diff --git a/packages/eslint-config/tsconfig.json b/packages/eslint-config/tsconfig.json index 61aa255661..7748061561 100644 --- a/packages/eslint-config/tsconfig.json +++ b/packages/eslint-config/tsconfig.json @@ -10,7 +10,7 @@ "strictNullChecks": true, "lib": ["esnext", "dom"], "moduleResolution": "node", - "resolveJsonModule": true + "noUncheckedIndexedAccess": true }, "exclude": ["dist"] } diff --git a/packages/nextra-theme-blog/src/components/meta.tsx b/packages/nextra-theme-blog/src/components/meta.tsx index a13584b098..da32fe6a38 100644 --- a/packages/nextra-theme-blog/src/components/meta.tsx +++ b/packages/nextra-theme-blog/src/components/meta.tsx @@ -32,9 +32,12 @@ export const Meta: FC = ({ {children} - {(author || date) && (readingTime || tags?.length) && ( - - )} + { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- fixme + (author || date) && (readingTime || tags?.length) && ( + + ) + } {readingTimeText || tagsEl} {readingTime && ( diff --git a/packages/nextra-theme-blog/src/mdx-components.tsx b/packages/nextra-theme-blog/src/mdx-components.tsx index 2ae082f9b4..ab17bcf027 100644 --- a/packages/nextra-theme-blog/src/mdx-components.tsx +++ b/packages/nextra-theme-blog/src/mdx-components.tsx @@ -98,8 +98,11 @@ export const useMDXComponents = ({ {dateObj && ( )} diff --git a/packages/nextra-theme-blog/tsconfig.json b/packages/nextra-theme-blog/tsconfig.json index 669156281f..a6413b99ac 100644 --- a/packages/nextra-theme-blog/tsconfig.json +++ b/packages/nextra-theme-blog/tsconfig.json @@ -10,7 +10,8 @@ "strictNullChecks": true, "jsx": "react-jsx", "moduleResolution": "bundler", - "types": ["vitest/globals"] + "types": ["vitest/globals"], + "noUncheckedIndexedAccess": true }, "exclude": ["dist"] } diff --git a/packages/nextra-theme-docs/src/components/locale-switch.tsx b/packages/nextra-theme-docs/src/components/locale-switch.tsx index b39a98e4e3..88bc819a61 100644 --- a/packages/nextra-theme-docs/src/components/locale-switch.tsx +++ b/packages/nextra-theme-docs/src/components/locale-switch.tsx @@ -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 @@ -29,7 +29,7 @@ export const LocaleSwitch: FC = ({ lite, className }) => { document.cookie = `NEXT_LOCALE=${lang}; expires=${date.toUTCString()}; path=/` location.href = addBasePath(pathname.replace(`/${locale}`, `/${lang}`)) }} - value={locale} + value={locale!} selectedOption={ diff --git a/packages/nextra-theme-docs/src/components/navbar/index.client.tsx b/packages/nextra-theme-docs/src/components/navbar/index.client.tsx index d981a76195..764dd324ae 100644 --- a/packages/nextra-theme-docs/src/components/navbar/index.client.tsx +++ b/packages/nextra-theme-docs/src/components/navbar/index.client.tsx @@ -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) || {} ).map(([key, item]) => ( <_MenuItem diff --git a/packages/nextra-theme-docs/src/components/sidebar.tsx b/packages/nextra-theme-docs/src/components/sidebar.tsx index a88500c29e..9e325d9c85 100644 --- a/packages/nextra-theme-docs/src/components/sidebar.tsx +++ b/packages/nextra-theme-docs/src/components/sidebar.tsx @@ -75,7 +75,7 @@ type FolderProps = { const Folder: FC = ({ 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 = @@ -99,19 +99,20 @@ const Folder: FC = ({ item, anchors, onFocus, level }) => { const [, rerender] = useState() - const handleClick: MouseEventHandler = useCallback(event => { - const el = event.currentTarget - const isClickOnIcon = - el /* will be always or