From f8e2f64316354b36099a01eef0735ec0970f5b99 Mon Sep 17 00:00:00 2001 From: Wolfgang Müller Date: Wed, 26 Mar 2025 19:40:28 +0100 Subject: frontend: Migrate to eslint-plugin-svelte 3.0 This includes a migration to a flat eslint.config.js, which will now also automatically ignore items from .gitignore. eslint-plugin-svelte 3.0 comes with a couple of changes to recommended rules as well, these are also addressed in this commit. --- frontend/eslint.config.js | 37 +++++ frontend/eslint.config.mjs | 99 ------------- frontend/package-lock.json | 165 ++++++++++++--------- frontend/package.json | 6 +- frontend/src/gql/Utils.ts | 2 + frontend/src/lib/Shortcuts.ts | 2 +- frontend/src/lib/Toasts.ts | 2 +- frontend/src/lib/Utils.ts | 1 - frontend/src/lib/components/Dialog.svelte | 1 + frontend/src/lib/dialogs/ConfirmDeletion.svelte | 2 +- .../dialogs/components/UpdateModeSelector.svelte | 2 +- frontend/src/lib/gallery/Gallery.svelte | 2 +- frontend/src/lib/pagination/Pagination.svelte | 2 +- frontend/src/lib/reader/PageView.svelte | 3 +- frontend/src/lib/scraper/ComicScrapeForm.svelte | 3 +- .../lib/scraper/components/SelectorGroup.svelte | 1 + frontend/src/lib/tabs/ArchiveDetails.svelte | 2 +- frontend/src/lib/tabs/ArchiveEdit.svelte | 2 +- frontend/src/lib/tabs/Tabs.svelte | 2 +- frontend/src/lib/toolbar/SelectItems.svelte | 2 +- frontend/src/lib/toolbar/SelectSort.svelte | 2 +- frontend/src/routes/+page.svelte | 6 +- 22 files changed, 159 insertions(+), 187 deletions(-) create mode 100644 frontend/eslint.config.js delete mode 100644 frontend/eslint.config.mjs diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..308fe8a --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,37 @@ +import { includeIgnoreFile } from '@eslint/compat'; +import js from '@eslint/js'; +import prettier from 'eslint-config-prettier'; +import svelte from 'eslint-plugin-svelte'; +import globals from 'globals'; +import { fileURLToPath } from 'node:url'; +import ts from 'typescript-eslint'; +import svelteConfig from './svelte.config.js'; + +const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); + +export default ts.config( + includeIgnoreFile(gitignorePath), + js.configs.recommended, + ...ts.configs.recommended, + ...svelte.configs.recommended, + prettier, + ...svelte.configs.prettier, + { + languageOptions: { + globals: { ...globals.browser, ...globals.node } + }, + rules: { 'no-undef': 'off' } + }, + { + files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], + ignores: ['eslint.config.js', 'svelte.config.js'], + languageOptions: { + parserOptions: { + projectService: true, + extraFileExtensions: ['.svelte'], + parser: ts.parser, + svelteConfig + } + } + } +); diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs deleted file mode 100644 index 9e842a8..0000000 --- a/frontend/eslint.config.mjs +++ /dev/null @@ -1,99 +0,0 @@ -import { FlatCompat } from '@eslint/eslintrc'; -import js from '@eslint/js'; -import typescriptEslint from '@typescript-eslint/eslint-plugin'; -import tsParser from '@typescript-eslint/parser'; -import globals from 'globals'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import parser from 'svelte-eslint-parser'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all -}); - -export default [ - { - ignores: [ - '**/*.cjs', - '**/.DS_Store', - '**/node_modules', - 'build', - '.svelte-kit', - 'package', - '**/.env', - '**/.env.*', - '!**/.env.example', - 'coverage', - '**/pnpm-lock.yaml', - '**/package-lock.json', - '**/yarn.lock', - 'src/gql', - 'eslint.config.mjs' - ] - }, - ...compat.extends( - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended-type-checked', - 'plugin:@typescript-eslint/stylistic-type-checked', - 'plugin:svelte/recommended', - 'prettier' - ), - { - plugins: { - '@typescript-eslint': typescriptEslint - }, - - languageOptions: { - globals: { - ...globals.browser, - ...globals.node - }, - - parser: tsParser, - ecmaVersion: 2022, - sourceType: 'module', - - parserOptions: { - extraFileExtensions: ['.svelte'], - project: true, - tsconfigRootDir: '/home/wolf/src/wolf/hircine/frontend' - } - }, - - rules: { - 'no-console': 'warn', - eqeqeq: 'error' - } - }, - { - files: ['**/*.svelte'], - - languageOptions: { - parser: parser, - ecmaVersion: 5, - sourceType: 'script', - - parserOptions: { - parser: '@typescript-eslint/parser' - } - }, - - rules: { - '@typescript-eslint/no-unsafe-argument': 'off', - '@typescript-eslint/no-unsafe-assignment': 'off', - '@typescript-eslint/no-unsafe-call': 'off', - '@typescript-eslint/no-unsafe-enum-comparison': 'off', - '@typescript-eslint/no-unsafe-member-access': 'off', - '@typescript-eslint/no-unused-expressions': 'off', - 'no-undef': 'off' - } - }, - ...compat.extends('plugin:@typescript-eslint/disable-type-checked').map((config) => ({ - ...config, - files: ['**/codegen.ts', '**/svelte.config.js'] - })) -]; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 349a0a2..bc5d7cf 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -17,6 +17,7 @@ "svelte-modals": "^2.0.0" }, "devDependencies": { + "@eslint/compat": "^1.2.5", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^9.19.0", "@graphql-codegen/cli": "^5.0.3", @@ -28,13 +29,11 @@ "@sveltejs/kit": "^2.8.1", "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/vite": "^4.0.6", - "@typescript-eslint/eslint-plugin": "^8.14.0", - "@typescript-eslint/parser": "^8.14.0", "@zerodevx/svelte-toast": "^0.9.6", "date-fns": "^4.1.0", "eslint": "^9.14.0", "eslint-config-prettier": "^10.0.0", - "eslint-plugin-svelte": "^2.46.0", + "eslint-plugin-svelte": "^3.0.0", "fast-deep-equal": "^3.1.3", "globals": "^16.0.0", "jsdom": "^26.0.0", @@ -47,6 +46,7 @@ "tailwindcss": "^4.0.0", "tslib": "^2.8.1", "typescript": "^5.6.3", + "typescript-eslint": "^8.28.0", "vite": "^6.0.0", "vitest": "^3.0.0" } @@ -1040,6 +1040,24 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/compat": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.7.tgz", + "integrity": "sha512-xvv7hJE32yhegJ8xNAnb62ggiAwTYHBpUCWhRxEj/ksvgDJuSXfoDkBcRYaYNFiJ+jH0IE3K16hd+xXzhBgNbg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, "node_modules/@eslint/config-array": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", @@ -4620,9 +4638,9 @@ } }, "node_modules/eslint-compat-utils": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", - "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.6.4.tgz", + "integrity": "sha512-/u+GQt8NMfXO8w17QendT4gvO5acfxQsAKirAt0LVxDnr2N8YLCVbregaNc/Yhp7NM128DwCaRvr8PLDfeNkQw==", "dev": true, "license": "MIT", "dependencies": { @@ -4662,32 +4680,31 @@ } }, "node_modules/eslint-plugin-svelte": { - "version": "2.46.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.1.tgz", - "integrity": "sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.3.3.tgz", + "integrity": "sha512-imzGqIgWbfsb/CR14d3k3M8MiVNGet+l9mjPhvo1Rm0Nxi0rNn4/eELqyR8FWlgKBMlGkOp2kshRJm0xpxNfHQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@jridgewell/sourcemap-codec": "^1.4.15", - "eslint-compat-utils": "^0.5.1", + "@eslint-community/eslint-utils": "^4.4.1", + "@jridgewell/sourcemap-codec": "^1.5.0", + "eslint-compat-utils": "^0.6.4", "esutils": "^2.0.3", "known-css-properties": "^0.35.0", - "postcss": "^8.4.38", + "postcss": "^8.4.49", "postcss-load-config": "^3.1.4", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.1.0", - "semver": "^7.6.2", - "svelte-eslint-parser": "^0.43.0" + "postcss-safe-parser": "^7.0.0", + "semver": "^7.6.3", + "svelte-eslint-parser": "^1.0.1" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", + "eslint": "^8.57.1 || ^9.0.0", "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "peerDependenciesMeta": { @@ -7280,20 +7297,30 @@ } }, "node_modules/postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "node": ">=18.0" }, "peerDependencies": { - "postcss": "^8.3.3" + "postcss": "^8.4.31" } }, "node_modules/postcss-scss": { @@ -7324,9 +7351,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", "dependencies": { @@ -8141,20 +8168,21 @@ } }, "node_modules/svelte-eslint-parser": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz", - "integrity": "sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.1.0.tgz", + "integrity": "sha512-JP0v/wzDXWxza6c8K9ZjKKHYfgt0KidlbWx1e9n9UV4q+o28GTkk71fR0IDZDmLUDYs3vSq0+Tm9fofDqzGe1w==", "dev": true, "license": "MIT", "dependencies": { - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "postcss": "^8.4.39", - "postcss-scss": "^4.0.9" + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.0", + "postcss": "^8.4.49", + "postcss-scss": "^4.0.9", + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://github.com/sponsors/ota-meshi" @@ -8168,36 +8196,14 @@ } } }, - "node_modules/svelte-eslint-parser/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/svelte-eslint-parser/node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "node_modules/svelte-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, + "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -8500,6 +8506,29 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.28.0.tgz", + "integrity": "sha512-jfZtxJoHm59bvoCMYCe2BM0/baMswRhMmYhy+w6VfcyHrjxZ0OJe0tGasydCpIpA+A/WIJhTyZfb3EtwNC/kHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.28.0", + "@typescript-eslint/parser": "8.28.0", + "@typescript-eslint/utils": "8.28.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, "node_modules/ua-parser-js": { "version": "1.0.40", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4c7d8ad..094cf39 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,7 @@ "test": "vitest" }, "devDependencies": { + "@eslint/compat": "^1.2.5", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^9.19.0", "@graphql-codegen/cli": "^5.0.3", @@ -26,13 +27,11 @@ "@sveltejs/kit": "^2.8.1", "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/vite": "^4.0.6", - "@typescript-eslint/eslint-plugin": "^8.14.0", - "@typescript-eslint/parser": "^8.14.0", "@zerodevx/svelte-toast": "^0.9.6", "date-fns": "^4.1.0", "eslint": "^9.14.0", "eslint-config-prettier": "^10.0.0", - "eslint-plugin-svelte": "^2.46.0", + "eslint-plugin-svelte": "^3.0.0", "fast-deep-equal": "^3.1.3", "globals": "^16.0.0", "jsdom": "^26.0.0", @@ -45,6 +44,7 @@ "tailwindcss": "^4.0.0", "tslib": "^2.8.1", "typescript": "^5.6.3", + "typescript-eslint": "^8.28.0", "vite": "^6.0.0", "vitest": "^3.0.0" }, diff --git a/frontend/src/gql/Utils.ts b/frontend/src/gql/Utils.ts index 177dff0..6fedd05 100644 --- a/frontend/src/gql/Utils.ts +++ b/frontend/src/gql/Utils.ts @@ -19,6 +19,7 @@ export function omitIdentifiers( return omit(obj, '__typename', 'id'); } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function isSuccess(object: any): object is gql.Success { if (object.__typename === undefined) { return false; @@ -27,6 +28,7 @@ export function isSuccess(object: any): object is gql.Success { return object.__typename.endsWith('Success') && (object as gql.Success).message !== undefined; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function isError(object: any): object is gql.Error { if (object.__typename === undefined) { return false; diff --git a/frontend/src/lib/Shortcuts.ts b/frontend/src/lib/Shortcuts.ts index 82f19ac..259500c 100644 --- a/frontend/src/lib/Shortcuts.ts +++ b/frontend/src/lib/Shortcuts.ts @@ -35,7 +35,7 @@ const modeSwitches = ['n', 'g', 'i', 'e'] as const; type ModeSwitch = (typeof modeSwitches)[number]; function isModeSwitch(s: string): s is ModeSwitch { - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument + // eslint-disable-next-line @typescript-eslint/no-explicit-any return modeSwitches.includes(s as any); } diff --git a/frontend/src/lib/Toasts.ts b/frontend/src/lib/Toasts.ts index abc9a7d..224989b 100644 --- a/frontend/src/lib/Toasts.ts +++ b/frontend/src/lib/Toasts.ts @@ -15,5 +15,5 @@ export function toastError(message: string) { }); } -// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any export const toastFinally = (reason: any) => toastError(reason); diff --git a/frontend/src/lib/Utils.ts b/frontend/src/lib/Utils.ts index df7dad8..c347544 100644 --- a/frontend/src/lib/Utils.ts +++ b/frontend/src/lib/Utils.ts @@ -35,7 +35,6 @@ export function getResultState(state: OperationResultState): ResultState { if (state.error) { message = `${state.error.name}: ${state.error.message}`; } else if (state.data) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const obj = Object.values(state.data)[0]; if (isError(obj)) { message = obj.message; diff --git a/frontend/src/lib/components/Dialog.svelte b/frontend/src/lib/components/Dialog.svelte index d300369..721d670 100644 --- a/frontend/src/lib/components/Dialog.svelte +++ b/frontend/src/lib/components/Dialog.svelte @@ -10,6 +10,7 @@ children?: Snippet; } + // eslint-disable-next-line svelte/no-unused-props let { isOpen, close, title, children }: Props = $props(); diff --git a/frontend/src/lib/dialogs/ConfirmDeletion.svelte b/frontend/src/lib/dialogs/ConfirmDeletion.svelte index dde4ea7..53b1dd4 100644 --- a/frontend/src/lib/dialogs/ConfirmDeletion.svelte +++ b/frontend/src/lib/dialogs/ConfirmDeletion.svelte @@ -31,7 +31,7 @@

{#if multiple} diff --git a/frontend/src/lib/dialogs/components/UpdateModeSelector.svelte b/frontend/src/lib/dialogs/components/UpdateModeSelector.svelte index 1a2307d..876657e 100644 --- a/frontend/src/lib/dialogs/components/UpdateModeSelector.svelte +++ b/frontend/src/lib/dialogs/components/UpdateModeSelector.svelte @@ -6,7 +6,7 @@
- {#each Object.entries(UpdateModeLabel) as [e, label]} + {#each Object.entries(UpdateModeLabel) as [e, label] (e)}