🚨(docs) upgrade eslint to v9 with Docs app

We upgraded ESLint to version 9 in the Docs app,
which includes several improvements and fixes.
This change also involves updating the ESLint
configuration files to the new format and ensuring
compatibility with the latest ESLint features.
This commit is contained in:
Anthony LC
2025-08-07 16:20:24 +02:00
parent 3688591dd1
commit 4184c339eb
18 changed files with 60 additions and 62 deletions

View File

@@ -1,14 +0,0 @@
module.exports = {
root: true,
extends: ['impress/next'],
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
settings: {
next: {
rootDir: __dirname,
},
},
ignorePatterns: ['node_modules', '.eslintrc.js', 'service-worker.js'],
};

View File

@@ -0,0 +1,24 @@
import { defineConfig } from '@eslint/config-helpers';
import docsPlugin from 'eslint-plugin-docs';
const eslintConfig = defineConfig([
{
plugins: {
docs: docsPlugin,
},
extends: ['docs/next'],
languageOptions: {
parserOptions: {
tsconfigRootDir: import.meta.dirname,
project: ['./tsconfig.json'],
},
},
settings: {
next: {
rootDir: import.meta.dirname,
},
},
},
]);
export default eslintConfig;

View File

@@ -32,7 +32,7 @@ export const useDropdownKeyboardNav = ({
.filter((index) => index !== -1);
switch (event.key) {
case 'ArrowDown':
case 'ArrowDown': {
event.preventDefault();
const nextIndex =
focusedIndex < enabledIndices.length - 1 ? focusedIndex + 1 : 0;
@@ -40,8 +40,9 @@ export const useDropdownKeyboardNav = ({
setFocusedIndex(nextIndex);
menuItemRefs.current[nextEnabledIndex]?.focus();
break;
}
case 'ArrowUp':
case 'ArrowUp': {
event.preventDefault();
const prevIndex =
focusedIndex > 0 ? focusedIndex - 1 : enabledIndices.length - 1;
@@ -49,9 +50,10 @@ export const useDropdownKeyboardNav = ({
setFocusedIndex(prevIndex);
menuItemRefs.current[prevEnabledIndex]?.focus();
break;
}
case 'Enter':
case ' ':
case ' ': {
event.preventDefault();
if (focusedIndex >= 0 && focusedIndex < enabledIndices.length) {
const selectedOptionIndex = enabledIndices[focusedIndex];
@@ -62,6 +64,7 @@ export const useDropdownKeyboardNav = ({
}
}
break;
}
case 'Escape':
event.preventDefault();

View File

@@ -55,7 +55,6 @@ export const QuickSearchInput = ({
</div>
)}
<Command.Input
/* eslint-disable-next-line jsx-a11y/no-autofocus */
autoFocus={true}
aria-label={t('Quick search input')}
onClick={(e) => {

View File

@@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
declare module '*.svg' {
import * as React from 'react';

View File

@@ -205,7 +205,7 @@ type ItemProps = Omit<ItemDefault, 'onClick'> & {
onClick: (e: React.MouseEvent) => void;
};
interface AIMenuItemTransform {
interface AIMenuItemTransformProps {
action: AITransformActions;
docId: string;
icon?: ReactNode;
@@ -216,7 +216,7 @@ const AIMenuItemTransform = ({
action,
children,
icon,
}: PropsWithChildren<AIMenuItemTransform>) => {
}: PropsWithChildren<AIMenuItemTransformProps>) => {
const { mutateAsync: requestAI, isPending } = useDocAITransform();
const editor = useBlockNoteEditor();
@@ -244,7 +244,7 @@ const AIMenuItemTransform = ({
);
};
interface AIMenuItemTranslate {
interface AIMenuItemTranslateProps {
language: string;
docId: string;
icon?: ReactNode;
@@ -255,7 +255,7 @@ const AIMenuItemTranslate = ({
docId,
icon,
language,
}: PropsWithChildren<AIMenuItemTranslate>) => {
}: PropsWithChildren<AIMenuItemTranslateProps>) => {
const { mutateAsync: requestAI, isPending } = useDocAITranslate();
const editor = useBlockNoteEditor();

View File

@@ -1,4 +1,3 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { createReactInlineContentSpec } from '@blocknote/react';
import { TFunction } from 'i18next';

View File

@@ -1,4 +1,3 @@
/* eslint-disable react-hooks/rules-of-hooks */
import {
PartialCustomInlineContentFromConfig,
StyleSchema,

View File

@@ -1,4 +1,3 @@
/* eslint-disable jsx-a11y/alt-text */
import { DefaultProps } from '@blocknote/core';
import { Image, Text, View } from '@react-pdf/renderer';

View File

@@ -1,4 +1,3 @@
/* eslint-disable jsx-a11y/alt-text */
import { Image, Link, Text } from '@react-pdf/renderer';
import DocSelectedIcon from '../assets/doc-selected.png';

View File

@@ -1,5 +1,3 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
import { useTreeContext } from '@gouvfr-lasuite/ui-kit';
import { Tooltip } from '@openfun/cunningham-react';
import React, { useCallback, useEffect, useState } from 'react';

View File

@@ -65,24 +65,20 @@ export function useDuplicateDoc(options?: DuplicateDocOptions) {
return useMutation<DuplicateDocResponse, APIError, DuplicateDocParams>({
mutationFn: async (variables) => {
try {
// Save the document if we can first, to ensure the latest state is duplicated
if (
variables.canSave &&
provider &&
provider.document.guid === variables.docId
) {
await updateDoc({
id: variables.docId,
content: toBase64(Y.encodeStateAsUpdate(provider.document)),
});
}
// Save the document if we can first, to ensure the latest state is duplicated
const canSave =
variables.canSave &&
provider &&
provider.document.guid === variables.docId;
return await duplicateDoc(variables);
} catch (error) {
// If save fails, throw the error to prevent duplication
throw error;
if (canSave) {
await updateDoc({
id: variables.docId,
content: toBase64(Y.encodeStateAsUpdate(provider.document)),
});
}
return await duplicateDoc(variables);
},
onSuccess: (data, variables, context) => {
void queryClient.resetQueries({

View File

@@ -43,7 +43,7 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
docId: currentDoc.id,
},
{
enabled: !!!treeContext?.root?.id,
enabled: !treeContext?.root?.id,
queryKey: [KEY_DOC_TREE, { id: currentDoc.id }],
},
);

View File

@@ -4,7 +4,6 @@ import { Doc, DocsResponse } from '@/docs/doc-management';
import { RequestData, RequestSerializer } from './RequestSerializer';
// eslint-disable-next-line import/order
import pkg from '@/../package.json';
export type DBRequest = {

View File

@@ -129,7 +129,6 @@ describe('ApiPlugin', () => {
const request = await apiPlugin.requestWillFetch?.(requestInit);
if (withClone) {
// eslint-disable-next-line jest/no-conditional-expect
expect(mockedClone).toHaveBeenCalled();
}

View File

@@ -24,6 +24,9 @@ export const isApiUrl = (href: string) => {
);
};
const isDocumentApiUrl = (url: URL) =>
isApiUrl(url.href) && /.*\/documents\/([a-z0-9-]+)\/$/g.test(url.href);
/**
* API routes
*/
@@ -45,8 +48,7 @@ registerRoute(
);
registerRoute(
({ url }) =>
isApiUrl(url.href) && url.href.match(/.*\/documents\/([a-z0-9\-]+)\/$/g),
({ url }) => isDocumentApiUrl(url),
new NetworkOnly({
plugins: [
new ApiPlugin({
@@ -61,8 +63,7 @@ registerRoute(
);
registerRoute(
({ url }) =>
isApiUrl(url.href) && url.href.match(/.*\/documents\/([a-z0-9\-]+)\/$/g),
({ url }) => isDocumentApiUrl(url),
new NetworkOnly({
plugins: [
new ApiPlugin({
@@ -90,8 +91,7 @@ registerRoute(
);
registerRoute(
({ url }) =>
isApiUrl(url.href) && url.href.match(/.*\/documents\/([a-z0-9\-]+)\/$/g),
({ url }) => isDocumentApiUrl(url),
new NetworkOnly({
plugins: [
new ApiPlugin({

View File

@@ -18,13 +18,11 @@ import {
StrategyOptions,
} from 'workbox-strategies';
// eslint-disable-next-line import/order
import { DAYS_EXP, SW_DEV_URL, SW_VERSION, getCacheNameVersion } from './conf';
import { ApiPlugin } from './plugins/ApiPlugin';
import { OfflinePlugin } from './plugins/OfflinePlugin';
import { isApiUrl } from './service-worker-api';
// eslint-disable-next-line import/order
import pkg from '@/../package.json';
declare const self: ServiceWorkerGlobalScope & {
@@ -130,12 +128,14 @@ setCatchHandler(async ({ request, url, event }) => {
case isApiUrl(url.href):
return ApiPlugin.getApiCatchHandler();
case request.destination === 'document':
if (url.pathname.match(/^\/docs\/([a-z0-9\-]+)\/$/g)) {
case request.destination === 'document': {
const isDocPath = /^\/docs\/([a-z0-9-]+)\/$/g.test(url.pathname);
if (isDocPath) {
return precacheStrategy.handle({ event, request: FALLBACK.docs });
}
return precacheStrategy.handle({ event, request: FALLBACK.offline });
}
case request.destination === 'image':
return precacheStrategy.handle({ event, request: FALLBACK.images });

View File

@@ -1,5 +1,5 @@
export const isValidEmail = (email: string) => {
return !!email.match(
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z\-0-9]{2,}))$/,
);
const EMAIL_REGEX =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z\-0-9]{2,}))$/;
return EMAIL_REGEX.test(email);
};