From d3dadeb010a4910454a43c37204c6e1eb711491f Mon Sep 17 00:00:00 2001 From: Samir Kamal <1954121+skamril@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:33:51 +0100 Subject: [PATCH] refactor(eslint): add new rules and fix new errors --- webapp/.eslintrc.cjs | 7 + webapp/package-lock.json | 120 +++++++++++++++++- webapp/package.json | 1 + .../App/Data/DatasetCreationDialog.tsx | 10 +- .../src/components/App/Data/MatrixDialog.tsx | 6 +- .../Singlestudy/explore/Debug/Data/Json.tsx | 8 +- .../explore/Results/ResultDetails/index.tsx | 8 +- .../Candidates/CreateCandidateDialog.tsx | 4 +- .../ExportModal/ExportFilter/index.tsx | 4 +- .../App/Studies/ExportModal/index.tsx | 5 +- webapp/src/components/common/LogModal.tsx | 6 +- webapp/src/hooks/useMemoLocked.ts | 1 + 12 files changed, 153 insertions(+), 27 deletions(-) diff --git a/webapp/.eslintrc.cjs b/webapp/.eslintrc.cjs index bd9d45c683..c1b4e10354 100644 --- a/webapp/.eslintrc.cjs +++ b/webapp/.eslintrc.cjs @@ -11,12 +11,14 @@ module.exports = { "plugin:react/recommended", "plugin:react/jsx-runtime", "plugin:react-hooks/recommended", + "plugin:jsdoc/recommended-typescript", "plugin:prettier/recommended", ], plugins: ["react-refresh"], ignorePatterns: ["dist", ".eslintrc.cjs"], parser: "@typescript-eslint/parser", parserOptions: { + // `ecmaVersion` is automatically sets by `esXXXX` in `env` sourceType: "module", project: ["./tsconfig.json", "./tsconfig.node.json"], tsconfigRootDir: __dirname, @@ -41,6 +43,9 @@ module.exports = { ], }, ], + curly: "error", + "jsdoc/require-jsdoc": "off", + "jsdoc/tag-lines": ["warn", "any", { "startLines": 1 }], // Expected 1 line after block description "no-param-reassign": [ "error", { @@ -65,6 +70,8 @@ module.exports = { "warn", { allowConstantExport: true }, ], + "react/hook-use-state": "error", "react/prop-types": "off", + "react/self-closing-comp": "error", }, }; diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 2c81f56aa3..3baefa00f7 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -92,6 +92,7 @@ "@vitejs/plugin-react-swc": "3.5.0", "eslint": "8.55.0", "eslint-config-prettier": "9.0.0", + "eslint-plugin-jsdoc": "48.2.0", "eslint-plugin-prettier": "5.0.0", "eslint-plugin-react": "7.33.2", "eslint-plugin-react-hooks": "4.6.0", @@ -479,6 +480,20 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.42.0.tgz", + "integrity": "sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw==", + "dev": true, + "dependencies": { + "comment-parser": "1.4.1", + "esquery": "^1.5.0", + "jsdoc-type-pratt-parser": "~4.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.19.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", @@ -3951,6 +3966,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -4287,6 +4311,18 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -4501,6 +4537,15 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5578,6 +5623,29 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-jsdoc": { + "version": "48.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.0.tgz", + "integrity": "sha512-O2B1XLBJnUCRkggFzUQ+PBYJDit8iAgXdlu8ucolqGrbmOWPvttZQZX8d1sC0MbqDMSLs8SHSQxaNPRY1RQREg==", + "dev": true, + "dependencies": { + "@es-joy/jsdoccomment": "~0.42.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.4", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.5.0", + "is-builtin-module": "^3.2.1", + "semver": "^7.6.0", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, "node_modules/eslint-plugin-prettier": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", @@ -7180,6 +7248,21 @@ "resolved": "https://registry.npmjs.org/is-browser/-/is-browser-2.1.0.tgz", "integrity": "sha512-F5rTJxDQ2sW81fcfOR1GnCXT6sVJC104fCyfj+mjpwNEwaPYSn5fte5jiHmBg3DHsIoL/l8Kvw5VN5SsTRcRFQ==" }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -7609,6 +7692,15 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -9999,9 +10091,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -10211,6 +10303,28 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "dev": true + }, "node_modules/split.js": { "version": "1.6.5", "resolved": "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz", diff --git a/webapp/package.json b/webapp/package.json index e21bd5dd7e..4a4c3e5645 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -95,6 +95,7 @@ "@vitejs/plugin-react-swc": "3.5.0", "eslint": "8.55.0", "eslint-config-prettier": "9.0.0", + "eslint-plugin-jsdoc": "48.2.0", "eslint-plugin-prettier": "5.0.0", "eslint-plugin-react": "7.33.2", "eslint-plugin-react-hooks": "4.6.0", diff --git a/webapp/src/components/App/Data/DatasetCreationDialog.tsx b/webapp/src/components/App/Data/DatasetCreationDialog.tsx index 41304025a2..c10b1eaaf4 100644 --- a/webapp/src/components/App/Data/DatasetCreationDialog.tsx +++ b/webapp/src/components/App/Data/DatasetCreationDialog.tsx @@ -50,9 +50,9 @@ function DatasetCreationDialog(props: PropTypes) { const [name, setName] = useState(""); const [isJson, setIsJson] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); - const [currentFile, setFile] = useState(); + const [currentFile, setCurrentFile] = useState(); const [importing, setImporting] = useState(false); - const [publicStatus, setPublic] = useState(false); + const [publicStatus, setPublicStatus] = useState(false); const onSave = async () => { let closeModal = true; @@ -93,7 +93,7 @@ function DatasetCreationDialog(props: PropTypes) { const onUpload = (e: ChangeEvent) => { const { target } = e; if (target && target.files && target.files.length === 1) { - setFile(target.files[0]); + setCurrentFile(target.files[0]); } }; @@ -116,7 +116,7 @@ function DatasetCreationDialog(props: PropTypes) { if (data) { setSelectedGroupList(data.groups); - setPublic(data.public); + setPublicStatus(data.public); setName(data.name); } } catch (e) { @@ -249,7 +249,7 @@ function DatasetCreationDialog(props: PropTypes) { {t("global.public")} setPublic(!publicStatus)} + onChange={() => setPublicStatus(!publicStatus)} inputProps={{ "aria-label": "primary checkbox" }} /> diff --git a/webapp/src/components/App/Data/MatrixDialog.tsx b/webapp/src/components/App/Data/MatrixDialog.tsx index ddd7a59b85..df2b14ab99 100644 --- a/webapp/src/components/App/Data/MatrixDialog.tsx +++ b/webapp/src/components/App/Data/MatrixDialog.tsx @@ -17,7 +17,7 @@ function MatrixDialog(props: PropTypes) { const [t] = useTranslation(); const enqueueErrorSnackbar = useEnqueueErrorSnackbar(); const [loading, setLoading] = useState(false); - const [matrix, setCurrentMatrix] = useState({ + const [matrix, setMatrix] = useState({ index: [], columns: [], data: [], @@ -34,7 +34,7 @@ function MatrixDialog(props: PropTypes) { columns: matrix ? res.columns : [], data: matrix ? res.data : [], }; - setCurrentMatrix(matrixContent); + setMatrix(matrixContent); } } catch (error) { enqueueErrorSnackbar(t("data.error.matrix"), error as AxiosError); @@ -44,7 +44,7 @@ function MatrixDialog(props: PropTypes) { }; init(); return () => { - setCurrentMatrix({ index: [], columns: [], data: [] }); + setMatrix({ index: [], columns: [], data: [] }); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [enqueueErrorSnackbar, matrixInfo, t]); diff --git a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx index 334fa84638..3923ce319a 100644 --- a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx @@ -22,7 +22,7 @@ function Json({ path, studyId }: Props) { const { enqueueSnackbar } = useSnackbar(); const enqueueErrorSnackbar = useEnqueueErrorSnackbar(); const [jsonData, setJsonData] = useState(null); - const [isSaveAllowed, setSaveAllowed] = useState(false); + const [isSaveAllowed, setIsSaveAllowed] = useState(false); const res = usePromiseWithSnackbarError( () => getStudyData(studyId, path, -1), @@ -34,7 +34,7 @@ function Json({ path, studyId }: Props) { // Reset save button when path changes useUpdateEffect(() => { - setSaveAllowed(false); + setIsSaveAllowed(false); }, [studyId, path]); //////////////////////////////////////////////////////////////// @@ -48,7 +48,7 @@ function Json({ path, studyId }: Props) { enqueueSnackbar(t("studies.success.saveData"), { variant: "success", }); - setSaveAllowed(false); + setIsSaveAllowed(false); } catch (e) { enqueueErrorSnackbar(t("studies.error.saveData"), e as AxiosError); } @@ -57,7 +57,7 @@ function Json({ path, studyId }: Props) { const handleJsonChange = (newJson: string) => { setJsonData(newJson); - setSaveAllowed(true); + setIsSaveAllowed(true); }; //////////////////////////////////////////////////////////////// diff --git a/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/index.tsx b/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/index.tsx index e1c3748b73..425245f9db 100644 --- a/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/index.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Results/ResultDetails/index.tsx @@ -62,7 +62,7 @@ function ResultDetails() { const { data: output } = outputRes; const [dataType, setDataType] = useState(DataType.General); - const [timestep, setTimeStep] = useState(Timestep.Hourly); + const [timestep, setTimestep] = useState(Timestep.Hourly); const [year, setYear] = useState(-1); const [itemType, setItemType] = useState(OutputItemType.Areas); const [selectedItemId, setSelectedItemId] = useState(""); @@ -151,7 +151,9 @@ function ResultDetails() { // !NOTE: Workaround to display the date in the correct format, to be replaced by a proper solution. const dateTimeFromIndex = useMemo(() => { - if (!matrixRes.data) return []; + if (!matrixRes.data) { + return []; + } // Annual format has a static string if (timestep === Timestep.Annual) { @@ -359,7 +361,7 @@ function ResultDetails() { size="small" variant="outlined" onChange={(event) => { - setTimeStep(event?.target.value as Timestep); + setTimestep(event?.target.value as Timestep); }} /> ), diff --git a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx index 40a4acad23..18366d7fe2 100644 --- a/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Xpansion/Candidates/CreateCandidateDialog.tsx @@ -22,14 +22,14 @@ interface PropType { function CreateCandidateDialog(props: PropType) { const { open, links, onClose, onSave } = props; const [t] = useTranslation(); - const [isToggled, setToggle] = useState(true); + const [isToggled, setIsToggled] = useState(true); //////////////////////////////////////////////////////////////// // Event Handlers //////////////////////////////////////////////////////////////// const handleToggle = () => { - setToggle(!isToggled); + setIsToggled(!isToggled); }; const handleSubmit = (data: SubmitHandlerPlus) => { diff --git a/webapp/src/components/App/Studies/ExportModal/ExportFilter/index.tsx b/webapp/src/components/App/Studies/ExportModal/ExportFilter/index.tsx index e751898f5a..e7a7fc9e85 100644 --- a/webapp/src/components/App/Studies/ExportModal/ExportFilter/index.tsx +++ b/webapp/src/components/App/Studies/ExportModal/ExportFilter/index.tsx @@ -35,7 +35,7 @@ interface PropTypes { function ExportFilterModal(props: PropTypes) { const [t] = useTranslation(); const { output, synthesis, filter, setFilter } = props; - const [year, setCurrentYear] = useState([]); + const [year, setYear] = useState([]); const [byYear, setByYear] = useState<{ isByYear: boolean; nbYear: number }>({ isByYear: false, nbYear: -1, @@ -105,7 +105,7 @@ function ExportFilterModal(props: PropTypes) { }))} data={year.map((elm) => elm.toString())} setValue={(value: string[] | string) => - setCurrentYear((value as string[]).map((elm) => parseInt(elm, 10))) + setYear((value as string[]).map((elm) => parseInt(elm, 10))) } sx={{ width: "100%", mb: 2 }} required diff --git a/webapp/src/components/App/Studies/ExportModal/index.tsx b/webapp/src/components/App/Studies/ExportModal/index.tsx index 83e426358d..0be51dda04 100644 --- a/webapp/src/components/App/Studies/ExportModal/index.tsx +++ b/webapp/src/components/App/Studies/ExportModal/index.tsx @@ -62,7 +62,8 @@ export default function ExportModal(props: BasicDialogProps & Props) { const [optionSelection, setOptionSelection] = useState("exportWith"); const [outputList, setOutputList] = useState(); const [currentOutput, setCurrentOutput] = useState(); - const [synthesis, setStudySynthesis] = useState(); + const [studySynthesis, setStudySynthesis] = + useState(); const [filter, setFilter] = useState({ type: StudyOutputDownloadType.AREAS, level: StudyOutputDownloadLevelDTO.WEEKLY, @@ -206,7 +207,7 @@ export default function ExportModal(props: BasicDialogProps & Props) { ( diff --git a/webapp/src/components/common/LogModal.tsx b/webapp/src/components/common/LogModal.tsx index 8dbcdeccb3..74032b95be 100644 --- a/webapp/src/components/common/LogModal.tsx +++ b/webapp/src/components/common/LogModal.tsx @@ -35,7 +35,7 @@ function LogModal(props: Props) { const [logDetail, setLogDetail] = useState(content); const divRef = useRef(null); const logRef = useRef(null); - const [autoscroll, setAutoScroll] = useState(true); + const [autoScroll, setAutoScroll] = useState(true); const [t] = useTranslation(); const updateLog = useCallback( @@ -92,11 +92,11 @@ function LogModal(props: Props) { useEffect(() => { if (logRef.current) { - if (autoscroll) { + if (autoScroll) { scrollToEnd(); } } - }, [logDetail, autoscroll]); + }, [logDetail, autoScroll]); useEffect(() => { if (followLogs) { diff --git a/webapp/src/hooks/useMemoLocked.ts b/webapp/src/hooks/useMemoLocked.ts index 6af425bbc6..b6e03535f9 100644 --- a/webapp/src/hooks/useMemoLocked.ts +++ b/webapp/src/hooks/useMemoLocked.ts @@ -10,6 +10,7 @@ import { useState } from "react"; */ function useMemoLocked(factory: () => T): T { + // eslint-disable-next-line react/hook-use-state const [state] = useState(factory); return state; }