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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
/* eslint-disable jsx-a11y/alt-text */
import { DefaultProps } from '@blocknote/core'; import { DefaultProps } from '@blocknote/core';
import { Image, Text, View } from '@react-pdf/renderer'; 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 { Image, Link, Text } from '@react-pdf/renderer';
import DocSelectedIcon from '../assets/doc-selected.png'; 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 { useTreeContext } from '@gouvfr-lasuite/ui-kit';
import { Tooltip } from '@openfun/cunningham-react'; import { Tooltip } from '@openfun/cunningham-react';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';

View File

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

View File

@@ -43,7 +43,7 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
docId: currentDoc.id, docId: currentDoc.id,
}, },
{ {
enabled: !!!treeContext?.root?.id, enabled: !treeContext?.root?.id,
queryKey: [KEY_DOC_TREE, { id: currentDoc.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'; import { RequestData, RequestSerializer } from './RequestSerializer';
// eslint-disable-next-line import/order
import pkg from '@/../package.json'; import pkg from '@/../package.json';
export type DBRequest = { export type DBRequest = {

View File

@@ -129,7 +129,6 @@ describe('ApiPlugin', () => {
const request = await apiPlugin.requestWillFetch?.(requestInit); const request = await apiPlugin.requestWillFetch?.(requestInit);
if (withClone) { if (withClone) {
// eslint-disable-next-line jest/no-conditional-expect
expect(mockedClone).toHaveBeenCalled(); 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 * API routes
*/ */
@@ -45,8 +48,7 @@ registerRoute(
); );
registerRoute( registerRoute(
({ url }) => ({ url }) => isDocumentApiUrl(url),
isApiUrl(url.href) && url.href.match(/.*\/documents\/([a-z0-9\-]+)\/$/g),
new NetworkOnly({ new NetworkOnly({
plugins: [ plugins: [
new ApiPlugin({ new ApiPlugin({
@@ -61,8 +63,7 @@ registerRoute(
); );
registerRoute( registerRoute(
({ url }) => ({ url }) => isDocumentApiUrl(url),
isApiUrl(url.href) && url.href.match(/.*\/documents\/([a-z0-9\-]+)\/$/g),
new NetworkOnly({ new NetworkOnly({
plugins: [ plugins: [
new ApiPlugin({ new ApiPlugin({
@@ -90,8 +91,7 @@ registerRoute(
); );
registerRoute( registerRoute(
({ url }) => ({ url }) => isDocumentApiUrl(url),
isApiUrl(url.href) && url.href.match(/.*\/documents\/([a-z0-9\-]+)\/$/g),
new NetworkOnly({ new NetworkOnly({
plugins: [ plugins: [
new ApiPlugin({ new ApiPlugin({

View File

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

View File

@@ -1,5 +1,5 @@
export const isValidEmail = (email: string) => { export const isValidEmail = (email: string) => {
return !!email.match( 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,}))$/, /^(([^<>()[\]\\.,;:\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);
}; };