diff --git a/.gitignore b/.gitignore
index 10379bdf..5a4915fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
.claude/
+app/node_modules/
+lib/node_modules/
data/
node_modules/
cypress/node_modules/
diff --git a/app/.eslintignore b/app/.eslintignore
deleted file mode 100644
index 2f3546e5..00000000
--- a/app/.eslintignore
+++ /dev/null
@@ -1,3 +0,0 @@
-node_modules/
-dist/
-data/
\ No newline at end of file
diff --git a/app/.eslintrc.cjs b/app/.eslintrc.cjs
deleted file mode 100644
index af43f19b..00000000
--- a/app/.eslintrc.cjs
+++ /dev/null
@@ -1,223 +0,0 @@
-// eslint-disable-next-line import/no-commonjs
-module.exports = {
- env: {
- browser: true,
- es2021: true,
- },
- extends: [
- 'standard',
- 'eslint:recommended',
- 'plugin:@eslint-community/eslint-comments/recommended',
- 'plugin:@typescript-eslint/recommended',
- 'plugin:import/recommended',
- 'plugin:import/typescript',
- // 'plugin:promise/recommended',
- 'plugin:security/recommended-legacy',
- 'plugin:react/recommended',
- ],
- parserOptions: {
- ecmaVersion: 'latest',
- parser: '@typescript-eslint/parser',
- sourceType: 'module',
- },
- plugins: [
- '@typescript-eslint',
- 'import',
- 'promise',
- 'security',
- 'no-catch-all',
- 'react',
- 'react-hooks',
- 'react-refresh',
- ],
- // TODO also parse this
- ignorePatterns: ['vite.config.ts'],
- settings: {
- 'import/resolver': {
- typescript: true,
- node: {
- extensions: ['.js', '.jsx', '.ts', '.tsx'],
- },
- },
- react: {
- version: '18.2.0',
- },
- },
- rules: {
- 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
- 'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
- 'react/react-in-jsx-scope': 'off', // Disable requirement for React import
- 'no-catch-all/no-catch-all': 'error',
- 'no-console': 'error',
- 'no-debugger': 'error',
- camelcase: 'error',
- indent: ['error', 2],
- 'linebreak-style': ['error', 'unix'],
- semi: ['error', 'never'],
- // Optional eslint-comments rule
- '@eslint-community/eslint-comments/no-unused-disable': 'error',
- '@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
- // import
- 'import/export': 'error',
- 'import/no-deprecated': 'error',
- 'import/no-empty-named-blocks': 'error',
- 'import/no-extraneous-dependencies': 'error',
- 'import/no-mutable-exports': 'error',
- 'import/no-unused-modules': 'error',
- 'import/no-named-as-default': 'error',
- 'import/no-named-as-default-member': 'error',
- 'import/no-amd': 'error',
- 'import/no-commonjs': 'error',
- 'import/no-import-module-exports': 'error',
- 'import/no-nodejs-modules': 'off',
- 'import/unambiguous': 'off', // not compatible with scriptless vue files
- 'import/default': 'error',
- 'import/named': 'error',
- 'import/namespace': 'error',
- 'import/no-absolute-path': 'error',
- 'import/no-cycle': 'error',
- 'import/no-dynamic-require': 'error',
- 'import/no-internal-modules': 'off',
- 'import/no-relative-packages': 'error',
- 'import/no-relative-parent-imports': [
- 'error',
- {
- ignore: ['#[src,types,root,components,utils,assets]/*', '@/config/*'],
- },
- ],
- 'import/no-self-import': 'error',
- 'import/no-unresolved': [
- 'error',
- {
- ignore: ['react'],
- },
- ],
- 'import/no-useless-path-segments': 'error',
- 'import/no-webpack-loader-syntax': 'error',
- 'import/consistent-type-specifier-style': 'error',
- 'import/exports-last': 'off',
- 'import/extensions': [
- 'error',
- 'never',
- {
- json: 'always',
- },
- ],
- 'import/first': 'error',
- 'import/group-exports': 'off',
- 'import/newline-after-import': 'error',
- 'import/no-anonymous-default-export': 'off', // todo - consider to enable again
- 'import/no-default-export': 'off', // incompatible with vite & vike
- 'import/no-duplicates': 'error',
- 'import/no-named-default': 'error',
- 'import/no-namespace': 'error',
- 'import/no-unassigned-import': [
- 'error',
- {
- allow: ['**/*.css'],
- },
- ],
- 'import/order': [
- 'error',
- {
- groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
- 'newlines-between': 'always',
- alphabetize: {
- order: 'asc', // sort in ascending order. Options: ["ignore", "asc", "desc"]
- caseInsensitive: true, // ignore case. Options: [true, false]
- },
- distinctGroup: true,
- },
- ],
- 'import/prefer-default-export': 'off',
- // promise
- 'promise/catch-or-return': 'error',
- 'promise/no-return-wrap': 'error',
- 'promise/param-names': 'error',
- 'promise/always-return': 'error',
- 'promise/no-native': 'off',
- 'promise/no-nesting': 'warn',
- 'promise/no-promise-in-callback': 'warn',
- 'promise/no-callback-in-promise': 'warn',
- 'promise/avoid-new': 'warn',
- 'promise/no-new-statics': 'error',
- 'promise/no-return-in-finally': 'warn',
- 'promise/valid-params': 'warn',
- 'promise/prefer-await-to-callbacks': 'error',
- 'promise/no-multiple-resolved': 'error',
- },
- overrides: [
- {
- files: ['*.ts', '*.tsx'],
- parser: '@typescript-eslint/parser',
- parserOptions: {
- tsconfigRootDir: __dirname,
- project: ['./tsconfig.json', '**/tsconfig.json'],
- ecmaVersion: 'latest',
- parser: '@typescript-eslint/parser',
- sourceType: 'module',
- },
- plugins: ['@typescript-eslint'],
- extends: [
- 'plugin:@typescript-eslint/recommended',
- 'plugin:@typescript-eslint/recommended-requiring-type-checking',
- 'plugin:@typescript-eslint/strict',
- ],
- rules: {
- '@typescript-eslint/consistent-type-imports': 'error',
- // allow explicitly defined dangling promises
- '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
- 'no-void': ['error', { allowAsStatement: true }],
- },
- },
- {
- files: ['!*.json'],
- plugins: ['prettier'],
- extends: ['plugin:prettier/recommended'],
- rules: {
- 'prettier/prettier': 'error',
- },
- },
- {
- files: ['*.json'],
- plugins: ['json'],
- extends: ['plugin:json/recommended-with-comments'],
- },
- // {
- // files: ['*.{test,spec}.[tj]s'],
- // plugins: ['vitest'],
- // extends: ['plugin:vitest/all'],
- // rules: {
- // 'vitest/prefer-lowercase-title': 'off',
- // 'vitest/no-hooks': 'off',
- // 'vitest/consistent-test-filename': 'off',
- // 'vitest/prefer-expect-assertions': [
- // 'off',
- // {
- // onlyFunctionsWithExpectInLoop: true,
- // onlyFunctionsWithExpectInCallback: true,
- // },
- // ],
- // 'vitest/prefer-strict-equal': 'off',
- // 'vitest/prefer-to-be-falsy': 'off',
- // 'vitest/prefer-to-be-truthy': 'off',
- // 'vitest/require-hook': [
- // 'error',
- // {
- // allowedFunctionCalls: [
- // 'mockClient.setRequestHandler',
- // 'setActivePinia',
- // 'provideApolloClient',
- // ],
- // },
- // ],
- // },
- // },
- {
- files: ['*.yaml', '*.yml'],
- parser: 'yaml-eslint-parser',
- plugins: ['yml'],
- extends: ['plugin:yml/prettier'],
- },
- ],
-}
diff --git a/app/.prettierrc.json b/app/.prettierrc.json
index 1db2a8cf..72e17590 100644
--- a/app/.prettierrc.json
+++ b/app/.prettierrc.json
@@ -11,4 +11,4 @@
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "auto"
-}
\ No newline at end of file
+}
diff --git a/app/eslint.config.js b/app/eslint.config.js
new file mode 100644
index 00000000..805ec2df
--- /dev/null
+++ b/app/eslint.config.js
@@ -0,0 +1,260 @@
+// ESLint v9 flat config for Utopia Map App
+import js from '@eslint/js'
+import eslintCommentsConfigs from '@eslint-community/eslint-plugin-eslint-comments/configs'
+import importXPlugin from 'eslint-plugin-import-x'
+import jsonPlugin from 'eslint-plugin-json'
+import noCatchAllPlugin from 'eslint-plugin-no-catch-all'
+import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
+import promisePlugin from 'eslint-plugin-promise'
+import react from 'eslint-plugin-react'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import securityPlugin from 'eslint-plugin-security'
+import globals from 'globals'
+import tseslint from 'typescript-eslint'
+
+export default tseslint.config(
+ // Ignore patterns
+ {
+ ignores: ['dist/**', 'node_modules/**', 'data/**', 'vite.config.ts'],
+ },
+
+ // Report unused eslint-disable directives (catches stale comments after rule renames)
+ {
+ linterOptions: {
+ reportUnusedDisableDirectives: 'error',
+ },
+ },
+
+ // Base ESLint recommended config
+ js.configs.recommended,
+
+ // ESLint comments recommended config
+ eslintCommentsConfigs.recommended,
+
+ // Security recommended config
+ securityPlugin.configs.recommended,
+
+ // React recommended configs
+ react.configs.flat.recommended,
+ react.configs.flat['jsx-runtime'],
+
+ // Main configuration for JavaScript/TypeScript files
+ {
+ files: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
+ languageOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ globals: {
+ ...globals.browser,
+ ...globals.es2021,
+ },
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ plugins: {
+ react: react,
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ 'import-x': importXPlugin,
+ promise: promisePlugin,
+ 'no-catch-all': noCatchAllPlugin,
+ },
+ settings: {
+ react: {
+ version: '18.2.0',
+ },
+ 'import-x/resolver': {
+ typescript: true,
+ node: {
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
+ },
+ },
+ },
+ rules: {
+ // ESLint comments rules - allow whole-file disables without eslint-enable
+ '@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
+
+ // Basic rules
+ 'no-console': 'error',
+ 'no-debugger': 'error',
+ camelcase: 'error',
+
+ // Standard JS rules (replacing eslint-config-standard)
+ semi: ['error', 'never'],
+ quotes: ['error', 'single', { avoidEscape: true }],
+ 'comma-dangle': ['error', 'always-multiline'],
+ // Disabled: conflicts with common TypeScript/React patterns
+ // 'space-before-function-paren': ['error', 'always'],
+ 'keyword-spacing': ['error', { before: true, after: true }],
+ 'space-infix-ops': 'error',
+ 'eol-last': ['error', 'always'],
+ 'no-trailing-spaces': 'error',
+ 'object-curly-spacing': ['error', 'always'],
+ 'array-bracket-spacing': ['error', 'never'],
+ 'computed-property-spacing': ['error', 'never'],
+ 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }],
+ // Disable indent rule due to known issues with TypeScript/JSX
+ // 'indent': ['error', 2],
+ 'linebreak-style': ['error', 'unix'],
+
+ // Additional standard rules that were missing
+ eqeqeq: ['error', 'always', { null: 'ignore' }],
+ 'new-cap': ['error', { newIsCap: true, capIsNew: false, properties: true }],
+ 'array-callback-return': ['error', { allowImplicit: false, checkForEach: false }],
+
+ // React rules
+ 'react/react-in-jsx-scope': 'off',
+ 'react/no-unescaped-entities': 'error',
+ 'react-hooks/rules-of-hooks': 'error',
+ 'react-hooks/exhaustive-deps': 'warn',
+ 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
+
+ // Import rules
+ 'import-x/export': 'error',
+ 'import-x/no-deprecated': 'error',
+ 'import-x/no-empty-named-blocks': 'error',
+ 'import-x/no-extraneous-dependencies': 'error',
+ 'import-x/no-mutable-exports': 'error',
+ 'import-x/no-named-as-default': 'error',
+ 'import-x/no-named-as-default-member': 'error',
+ 'import-x/no-amd': 'error',
+ 'import-x/no-commonjs': 'error',
+ 'import-x/no-nodejs-modules': 'off',
+ 'import-x/default': 'error',
+ 'import-x/named': 'error',
+ 'import-x/namespace': 'error',
+ 'import-x/no-absolute-path': 'error',
+ 'import-x/no-cycle': 'error',
+ 'import-x/no-dynamic-require': 'error',
+ 'import-x/no-internal-modules': 'off',
+ 'import-x/no-relative-packages': 'error',
+ 'import-x/no-self-import': 'error',
+ 'import-x/no-unresolved': ['error', { ignore: ['react'] }],
+ 'import-x/no-useless-path-segments': 'error',
+ 'import-x/no-webpack-loader-syntax': 'error',
+ 'import-x/consistent-type-specifier-style': 'error',
+ 'import-x/exports-last': 'off',
+ 'import-x/extensions': ['error', 'never', { json: 'always' }],
+ 'import-x/first': 'error',
+ 'import-x/group-exports': 'off',
+ 'import-x/newline-after-import': 'error',
+ 'import-x/no-anonymous-default-export': 'off',
+ 'import-x/no-default-export': 'off',
+ 'import-x/no-duplicates': 'error',
+ 'import-x/no-named-default': 'error',
+ 'import-x/no-namespace': 'error',
+ 'import-x/no-unassigned-import': ['error', { allow: ['**/*.css'] }],
+ 'import-x/order': [
+ 'error',
+ {
+ groups: [
+ 'builtin',
+ 'external',
+ 'internal',
+ 'parent',
+ 'sibling',
+ 'index',
+ 'object',
+ 'type',
+ ],
+ 'newlines-between': 'always',
+ alphabetize: {
+ order: 'asc',
+ caseInsensitive: true,
+ },
+ distinctGroup: true,
+ },
+ ],
+ 'import-x/prefer-default-export': 'off',
+
+ // Promise rules
+ 'promise/catch-or-return': 'error',
+ 'promise/no-return-wrap': 'error',
+ 'promise/param-names': 'error',
+ 'promise/always-return': 'error',
+ 'promise/no-native': 'off',
+ 'promise/no-nesting': 'warn',
+ 'promise/no-promise-in-callback': 'warn',
+ 'promise/no-callback-in-promise': 'warn',
+ 'promise/avoid-new': 'warn',
+ 'promise/no-new-statics': 'error',
+ 'promise/no-return-in-finally': 'warn',
+ 'promise/valid-params': 'warn',
+ 'promise/prefer-await-to-callbacks': 'error',
+ 'promise/no-multiple-resolved': 'error',
+
+ // Security and other rules
+ 'no-catch-all/no-catch-all': 'error',
+
+ // Additional import rules
+ 'import-x/no-unused-modules': 'error',
+ 'import-x/no-import-module-exports': 'error',
+ 'import-x/unambiguous': 'off',
+ 'import-x/no-relative-parent-imports': [
+ 'error',
+ {
+ ignore: ['#[src,types,root,components,utils,assets]/*', '@/config/*'],
+ },
+ ],
+ },
+ },
+
+ // TypeScript configs (applied after main config)
+ ...tseslint.configs.recommended,
+ ...tseslint.configs.strict,
+ ...tseslint.configs.stylistic,
+
+ // TypeScript type-checking configuration
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ ...tseslint.configs.recommendedTypeChecked,
+ ...tseslint.configs.strictTypeChecked,
+ ...tseslint.configs.stylisticTypeChecked,
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ rules: {
+ '@typescript-eslint/consistent-type-imports': 'error',
+ '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
+ 'no-void': ['error', { allowAsStatement: true }],
+
+ // Disable empty function rule - legitimate use in React contexts and empty constructors
+ '@typescript-eslint/no-empty-function': 'off',
+
+ // Configure no-unused-expressions to allow logical AND and ternary patterns
+ '@typescript-eslint/no-unused-expressions': [
+ 'error',
+ {
+ allowShortCircuit: true,
+ allowTernary: true,
+ },
+ ],
+ },
+ },
+
+ // JSON files configuration
+ {
+ files: ['**/*.json'],
+ plugins: {
+ json: jsonPlugin,
+ },
+ rules: {
+ // Disable TypeScript-specific rules for JSON files
+ '@typescript-eslint/no-unused-expressions': 'off',
+ // JSON-specific rules
+ 'json/*': 'error',
+ },
+ },
+
+ // Prettier recommended config (should be last to override other formatting rules)
+ eslintPluginPrettierRecommended,
+)
diff --git a/app/package.json b/app/package.json
index a7fbb136..74eccabf 100644
--- a/app/package.json
+++ b/app/package.json
@@ -9,7 +9,7 @@
"scripts": {
"dev": "vite --host",
"build": "tsc && vite build",
- "test:lint:eslint": "eslint --ext .ts,.tsx,.js,.jsx,.cjs,.mjs,.json,.yml,.yaml --max-warnings 0 .",
+ "test:lint:eslint": "eslint --max-warnings 0 .",
"preview": "vite preview"
},
"dependencies": {
@@ -22,36 +22,33 @@
"react-dom": "^18.2.0",
"react-rnd": "^10.4.1",
"react-router-dom": "^6.23.0",
- "vite-tsconfig-paths": "^5.1.4",
- "utopia-ui": "^3.0.111"
+ "utopia-ui": "^3.0.111",
+ "vite-tsconfig-paths": "^5.1.4"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
+ "@eslint/js": "^9.36.0",
"@types/node": "^24.10.2",
"@types/react": "^18.2.79",
"@types/react-dom": "^18.2.25",
- "@typescript-eslint/eslint-plugin": "^5.62.0",
- "@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-react": "^4.0.0",
"daisyui": "^5.5.5",
- "eslint": "^8.24.0",
- "eslint-config-prettier": "^10.1.8",
- "eslint-config-standard": "^17.1.0",
+ "eslint": "^9.36.0",
"eslint-import-resolver-typescript": "^4.4.4",
- "eslint-plugin-import": "^2.31.0",
+ "eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-json": "^3.1.0",
- "eslint-plugin-n": "^17.23.1",
"eslint-plugin-no-catch-all": "^1.1.0",
- "eslint-plugin-prettier": "^5.2.1",
+ "eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"eslint-plugin-security": "^3.0.1",
- "eslint-plugin-yml": "^1.14.0",
+ "globals": "^16.3.0",
"postcss": "^8.4.30",
"tailwindcss": "^4.1.17",
"typescript": "^5.9.3",
+ "typescript-eslint": "^8.9.0",
"vite": "^7.2.7",
"vite-plugin-pwa": "^1.2.0"
}
diff --git a/app/src/App.tsx b/app/src/App.tsx
index 36a3de38..e21ef608 100644
--- a/app/src/App.tsx
+++ b/app/src/App.tsx
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
-/* eslint-disable import/order */
+/* eslint-disable import-x/order */
/* eslint-disable eqeqeq */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable react-hooks/exhaustive-deps */
@@ -8,6 +8,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable new-cap */
/* eslint-disable @typescript-eslint/prefer-optional-chain */
+/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
+/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
diff --git a/app/src/ModalContent.tsx b/app/src/ModalContent.tsx
index f501ddab..c79a850b 100644
--- a/app/src/ModalContent.tsx
+++ b/app/src/ModalContent.tsx
@@ -2,6 +2,7 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
+/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { useEffect, useState } from 'react'
import { TextView } from 'utopia-ui'
@@ -22,7 +23,9 @@ export function Welcome1({ clickAction1, map }: ChapterProps) {
@@ -45,7 +48,9 @@ export function Welcome1({ clickAction1, map }: ChapterProps) {
diff --git a/app/src/api/directus.ts b/app/src/api/directus.ts
index 140ca4ba..2e28ed01 100644
--- a/app/src/api/directus.ts
+++ b/app/src/api/directus.ts
@@ -4,7 +4,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { createDirectus, rest, authentication } from '@directus/sdk'
-// eslint-disable-next-line import/no-relative-parent-imports
+// eslint-disable-next-line import-x/no-relative-parent-imports
import { config } from '../config'
import type { AuthenticationData, AuthenticationStorage } from '@directus/sdk'
@@ -86,9 +86,10 @@ export const authLocalStorage = (mainKey = 'directus_storage') =>
// implementation of set, here set the value at mainKey in localStorage, or remove it if value is null
set: async (value: AuthenticationData | null) => {
if (!value) {
- return window.localStorage.removeItem(mainKey)
+ window.localStorage.removeItem(mainKey)
+ return
}
- return window.localStorage.setItem(mainKey, JSON.stringify(value))
+ window.localStorage.setItem(mainKey, JSON.stringify(value))
},
}) as AuthenticationStorage
diff --git a/app/src/api/inviteApi.ts b/app/src/api/inviteApi.ts
index 7a6d2c24..8dcd8d8a 100644
--- a/app/src/api/inviteApi.ts
+++ b/app/src/api/inviteApi.ts
@@ -1,4 +1,3 @@
-/* @eslint-disable-next-line import/no-relative-parent-imports */
import { config } from '@/config'
import type { UserApi } from 'utopia-ui'
diff --git a/app/src/api/itemsApi.ts b/app/src/api/itemsApi.ts
index defb7c99..7b60142f 100644
--- a/app/src/api/itemsApi.ts
+++ b/app/src/api/itemsApi.ts
@@ -20,8 +20,8 @@ export class itemsApi
implements ItemsApi {
constructor(
collectionName: keyof MyCollections,
- layerId?: string | undefined,
- mapId?: string | undefined,
+ layerId?: string,
+ mapId?: string,
filter?: any,
customParameter?: any,
) {
@@ -111,8 +111,8 @@ export class itemsApi implements ItemsApi {
async deleteItem(id: string): Promise {
try {
- const result = await directusClient.request(deleteItem(this.collectionName, id))
- return result as unknown as boolean
+ await directusClient.request(deleteItem(this.collectionName, id))
+ return true
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
diff --git a/app/src/api/layersApi.ts b/app/src/api/layersApi.ts
index b91e5881..fa779ecf 100644
--- a/app/src/api/layersApi.ts
+++ b/app/src/api/layersApi.ts
@@ -21,6 +21,7 @@ export class layersApi {
{ itemType: ['*.*', { profileTemplate: ['*', 'item.*.*.*.*'] }] },
{ markerIcon: ['*'] } as any,
],
+ // eslint-disable-next-line camelcase
filter: { maps: { maps_id: { id: { _eq: this.mapId } } } },
limit: 500,
sort: ['sort'],
diff --git a/app/src/api/permissionsApi.ts b/app/src/api/permissionsApi.ts
index c0f42647..ed1c027e 100644
--- a/app/src/api/permissionsApi.ts
+++ b/app/src/api/permissionsApi.ts
@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-useless-constructor */
-/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
import { readPermissions } from '@directus/sdk'
diff --git a/app/src/api/refiBcnApi.ts b/app/src/api/refiBcnApi.ts
index 5f685ed8..65717ad3 100644
--- a/app/src/api/refiBcnApi.ts
+++ b/app/src/api/refiBcnApi.ts
@@ -1,7 +1,7 @@
/* eslint-disable no-console */
-/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-disable @typescript-eslint/no-unsafe-return */
import axios from 'axios'
import type { ItemsApi } from 'utopia-ui'
diff --git a/app/src/api/userApi.ts b/app/src/api/userApi.ts
index c63e1baa..ff8e7744 100644
--- a/app/src/api/userApi.ts
+++ b/app/src/api/userApi.ts
@@ -3,7 +3,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-explicit-any */
-/* eslint-disable @typescript-eslint/no-unsafe-return */
import { createUser, passwordRequest, passwordReset, readMe, updateMe } from '@directus/sdk'
import { directusClient } from './directus'
@@ -54,7 +53,8 @@ export class UserApi {
async logout(): Promise {
try {
- return await directusClient.logout()
+ await directusClient.logout()
+ return
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
@@ -98,7 +98,8 @@ export class UserApi {
async requestPasswordReset(email: string, reset_url?: string): Promise {
try {
- return await directusClient.request(passwordRequest(email, reset_url))
+ await directusClient.request(passwordRequest(email, reset_url))
+ return
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
@@ -108,7 +109,8 @@ export class UserApi {
async passwordReset(reset_token: string, new_password: string): Promise {
try {
- return await directusClient.request(passwordReset(reset_token, new_password))
+ await directusClient.request(passwordReset(reset_token, new_password))
+ return
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
diff --git a/app/src/main.tsx b/app/src/main.tsx
index 86140f18..ccb66470 100644
--- a/app/src/main.tsx
+++ b/app/src/main.tsx
@@ -1,5 +1,4 @@
-/* eslint-disable import/extensions */
-/* eslint-disable import/no-named-as-default-member */
+/* eslint-disable import-x/extensions */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React from 'react'
import ReactDOM from 'react-dom/client'
diff --git a/app/src/pages/Landingpage.tsx b/app/src/pages/Landingpage.tsx
index a5d221fa..97c355c0 100644
--- a/app/src/pages/Landingpage.tsx
+++ b/app/src/pages/Landingpage.tsx
@@ -1,7 +1,7 @@
/* eslint-disable react/no-unescaped-entities */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
-/* eslint-disable import/no-relative-parent-imports */
+/* eslint-disable import-x/no-relative-parent-imports */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable new-cap */
diff --git a/app/src/pages/MapContainer.tsx b/app/src/pages/MapContainer.tsx
index 0d305d3d..f0b0cd9f 100644
--- a/app/src/pages/MapContainer.tsx
+++ b/app/src/pages/MapContainer.tsx
@@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-explicit-any */
-/* eslint-disable import/no-relative-parent-imports */
+/* eslint-disable import-x/no-relative-parent-imports */
/* eslint-disable new-cap */
-/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
+/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { useEffect, useState } from 'react'
import {
diff --git a/app/src/pages/data.ts b/app/src/pages/data.ts
index f72437dc..cd36c415 100644
--- a/app/src/pages/data.ts
+++ b/app/src/pages/data.ts
@@ -1,3 +1,4 @@
+/* eslint-disable camelcase */
import type { Item, Tag } from 'utopia-ui'
export const tags: Tag[] = [
diff --git a/app/tsconfig.json b/app/tsconfig.json
index 95a0cf49..784becfb 100644
--- a/app/tsconfig.json
+++ b/app/tsconfig.json
@@ -12,35 +12,17 @@
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
- "@/*": [
- "src/*"
- ],
- "utopia-ui": [
- "../lib/src"
- ],
- "#components/*": [
- "../lib/src/Components/*"
- ],
- "#utils/*": [
- "../lib/src/Utils/*"
- ],
- "#types/*": [
- "../lib/src/types/*"
- ],
- "#assets/*": [
- "../lib/src/assets/*"
- ],
- "#src/*": [
- "../lib/src/*"
- ],
- "#root/*": [
- "../lib/*"
- ]
+ "@/*": ["src/*"],
+ "utopia-ui": ["../lib/src"],
+ "#components/*": ["../lib/src/Components/*"],
+ "#utils/*": ["../lib/src/Utils/*"],
+ "#types/*": ["../lib/src/types/*"],
+ "#assets/*": ["../lib/src/assets/*"],
+ "#src/*": ["../lib/src/*"],
+ "#root/*": ["../lib/*"]
}
},
- "include": [
- "src"
- ],
+ "include": ["src"],
"references": [
{
"path": "./tsconfig.node.json"
diff --git a/lib/.eslintignore b/lib/.eslintignore
deleted file mode 100644
index c24c07ac..00000000
--- a/lib/.eslintignore
+++ /dev/null
@@ -1,5 +0,0 @@
-node_modules/
-dist/
-examples/
-docs/
-coverage/
\ No newline at end of file
diff --git a/lib/.eslintrc.cjs b/lib/.eslintrc.cjs
deleted file mode 100644
index e4a3299b..00000000
--- a/lib/.eslintrc.cjs
+++ /dev/null
@@ -1,221 +0,0 @@
-// eslint-disable-next-line import/no-commonjs
-module.exports = {
- env: {
- browser: true,
- es2021: true,
- },
- extends: [
- 'standard',
- 'eslint:recommended',
- 'plugin:@eslint-community/eslint-comments/recommended',
- 'plugin:@typescript-eslint/recommended',
- 'plugin:import/recommended',
- 'plugin:import/typescript',
- // 'plugin:promise/recommended',
- 'plugin:security/recommended-legacy',
- 'plugin:react/recommended',
- ],
- parserOptions: {
- ecmaVersion: 'latest',
- parser: '@typescript-eslint/parser',
- sourceType: 'module',
- },
- plugins: [
- '@typescript-eslint',
- 'import',
- 'promise',
- 'security',
- 'no-catch-all',
- 'react',
- 'react-hooks',
- 'react-refresh',
- ],
- settings: {
- 'import/resolver': {
- typescript: true,
- node: {
- extensions: ['.js', '.jsx', '.ts', '.tsx'],
- },
- },
- react: {
- version: '18.2.0',
- },
- },
- rules: {
- 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
- 'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
- 'react/react-in-jsx-scope': 'off', // Disable requirement for React import
- 'no-catch-all/no-catch-all': 'error',
- 'no-console': 'error',
- 'no-debugger': 'error',
- camelcase: 'error',
- indent: ['error', 2],
- 'linebreak-style': ['error', 'unix'],
- semi: ['error', 'never'],
- // Optional eslint-comments rule
- '@eslint-community/eslint-comments/no-unused-disable': 'error',
- '@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
- // import
- 'import/export': 'error',
- 'import/no-deprecated': 'error',
- 'import/no-empty-named-blocks': 'error',
- 'import/no-extraneous-dependencies': 'error',
- 'import/no-mutable-exports': 'error',
- 'import/no-unused-modules': 'error',
- 'import/no-named-as-default': 'error',
- 'import/no-named-as-default-member': 'error',
- 'import/no-amd': 'error',
- 'import/no-commonjs': 'error',
- 'import/no-import-module-exports': 'error',
- 'import/no-nodejs-modules': 'off',
- 'import/unambiguous': 'off', // not compatible with scriptless vue files
- 'import/default': 'error',
- 'import/named': 'error',
- 'import/namespace': 'error',
- 'import/no-absolute-path': 'error',
- 'import/no-cycle': 'error',
- 'import/no-dynamic-require': 'error',
- 'import/no-internal-modules': 'off',
- 'import/no-relative-packages': 'error',
- 'import/no-relative-parent-imports': [
- 'error',
- {
- ignore: ['#[src,types,root,components,utils,assets]/*'],
- },
- ],
- 'import/no-self-import': 'error',
- 'import/no-unresolved': [
- 'error',
- {
- ignore: ['react'],
- },
- ],
- 'import/no-useless-path-segments': 'error',
- 'import/no-webpack-loader-syntax': 'error',
- 'import/consistent-type-specifier-style': 'error',
- 'import/exports-last': 'off',
- 'import/extensions': [
- 'error',
- 'never',
- {
- json: 'always',
- },
- ],
- 'import/first': 'error',
- 'import/group-exports': 'off',
- 'import/newline-after-import': 'error',
- 'import/no-anonymous-default-export': 'off', // todo - consider to enable again
- 'import/no-default-export': 'off', // incompatible with vite & vike
- 'import/no-duplicates': 'error',
- 'import/no-named-default': 'error',
- 'import/no-namespace': 'error',
- 'import/no-unassigned-import': [
- 'error',
- {
- allow: ['**/*.css'],
- },
- ],
- 'import/order': [
- 'error',
- {
- groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
- 'newlines-between': 'always',
- alphabetize: {
- order: 'asc', // sort in ascending order. Options: ["ignore", "asc", "desc"]
- caseInsensitive: true, // ignore case. Options: [true, false]
- },
- distinctGroup: true,
- },
- ],
- 'import/prefer-default-export': 'off',
- // promise
- 'promise/catch-or-return': 'error',
- 'promise/no-return-wrap': 'error',
- 'promise/param-names': 'error',
- 'promise/always-return': 'error',
- 'promise/no-native': 'off',
- 'promise/no-nesting': 'warn',
- 'promise/no-promise-in-callback': 'warn',
- 'promise/no-callback-in-promise': 'warn',
- 'promise/avoid-new': 'warn',
- 'promise/no-new-statics': 'error',
- 'promise/no-return-in-finally': 'warn',
- 'promise/valid-params': 'warn',
- 'promise/prefer-await-to-callbacks': 'error',
- 'promise/no-multiple-resolved': 'error',
- },
- overrides: [
- {
- files: ['*.ts', '*.tsx'],
- parser: '@typescript-eslint/parser',
- parserOptions: {
- tsconfigRootDir: __dirname,
- project: ['./tsconfig.json', '**/tsconfig.json'],
- ecmaVersion: 'latest',
- parser: '@typescript-eslint/parser',
- sourceType: 'module',
- },
- plugins: ['@typescript-eslint'],
- extends: [
- 'plugin:@typescript-eslint/recommended',
- 'plugin:@typescript-eslint/recommended-requiring-type-checking',
- 'plugin:@typescript-eslint/strict',
- ],
- rules: {
- '@typescript-eslint/consistent-type-imports': 'error',
- // allow explicitly defined dangling promises
- '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
- 'no-void': ['error', { allowAsStatement: true }],
- },
- },
- {
- files: ['!*.json'],
- plugins: ['prettier'],
- extends: ['plugin:prettier/recommended'],
- rules: {
- 'prettier/prettier': 'error',
- },
- },
- {
- files: ['*.json'],
- plugins: ['json'],
- extends: ['plugin:json/recommended-with-comments'],
- },
- // {
- // files: ['*.{test,spec}.[tj]s'],
- // plugins: ['vitest'],
- // extends: ['plugin:vitest/all'],
- // rules: {
- // 'vitest/prefer-lowercase-title': 'off',
- // 'vitest/no-hooks': 'off',
- // 'vitest/consistent-test-filename': 'off',
- // 'vitest/prefer-expect-assertions': [
- // 'off',
- // {
- // onlyFunctionsWithExpectInLoop: true,
- // onlyFunctionsWithExpectInCallback: true,
- // },
- // ],
- // 'vitest/prefer-strict-equal': 'off',
- // 'vitest/prefer-to-be-falsy': 'off',
- // 'vitest/prefer-to-be-truthy': 'off',
- // 'vitest/require-hook': [
- // 'error',
- // {
- // allowedFunctionCalls: [
- // 'mockClient.setRequestHandler',
- // 'setActivePinia',
- // 'provideApolloClient',
- // ],
- // },
- // ],
- // },
- // },
- {
- files: ['*.yaml', '*.yml'],
- parser: 'yaml-eslint-parser',
- plugins: ['yml'],
- extends: ['plugin:yml/prettier'],
- },
- ],
-}
diff --git a/lib/.prettierrc.json b/lib/.prettierrc.json
index 1db2a8cf..72e17590 100644
--- a/lib/.prettierrc.json
+++ b/lib/.prettierrc.json
@@ -11,4 +11,4 @@
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "auto"
-}
\ No newline at end of file
+}
diff --git a/lib/cypress/support/component.ts b/lib/cypress/support/component.ts
index 13ea7ddf..86f1ddf0 100644
--- a/lib/cypress/support/component.ts
+++ b/lib/cypress/support/component.ts
@@ -14,7 +14,7 @@
// ***********************************************************
// Import commands.js using ES2015 syntax:
-// eslint-disable-next-line import/no-unassigned-import
+// eslint-disable-next-line import-x/no-unassigned-import
import './commands'
import { mount } from 'cypress/react'
diff --git a/lib/eslint.config.js b/lib/eslint.config.js
new file mode 100644
index 00000000..855a03ad
--- /dev/null
+++ b/lib/eslint.config.js
@@ -0,0 +1,260 @@
+// ESLint v9 flat config for Utopia UI Library
+import js from '@eslint/js'
+import eslintCommentsConfigs from '@eslint-community/eslint-plugin-eslint-comments/configs'
+import importXPlugin from 'eslint-plugin-import-x'
+import jsonPlugin from 'eslint-plugin-json'
+import noCatchAllPlugin from 'eslint-plugin-no-catch-all'
+import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
+import promisePlugin from 'eslint-plugin-promise'
+import react from 'eslint-plugin-react'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import securityPlugin from 'eslint-plugin-security'
+import globals from 'globals'
+import tseslint from 'typescript-eslint'
+
+export default tseslint.config(
+ // Ignore patterns
+ {
+ ignores: ['dist/**', 'node_modules/**', 'coverage/**', 'docs/**', 'examples/**'],
+ },
+
+ // Report unused eslint-disable directives (catches stale comments after rule renames)
+ {
+ linterOptions: {
+ reportUnusedDisableDirectives: 'error',
+ },
+ },
+
+ // Base ESLint recommended config
+ js.configs.recommended,
+
+ // ESLint comments recommended config
+ eslintCommentsConfigs.recommended,
+
+ // Security recommended config
+ securityPlugin.configs.recommended,
+
+ // React recommended configs
+ react.configs.flat.recommended,
+ react.configs.flat['jsx-runtime'],
+
+ // Main configuration for JavaScript/TypeScript files
+ {
+ files: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
+ languageOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ globals: {
+ ...globals.browser,
+ ...globals.es2021,
+ },
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ plugins: {
+ react: react,
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ 'import-x': importXPlugin,
+ promise: promisePlugin,
+ 'no-catch-all': noCatchAllPlugin,
+ },
+ settings: {
+ react: {
+ version: '18.2.0',
+ },
+ 'import-x/resolver': {
+ typescript: true,
+ node: {
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
+ },
+ },
+ },
+ rules: {
+ // ESLint comments rules - allow whole-file disables without eslint-enable
+ '@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
+
+ // Basic rules
+ 'no-console': 'error',
+ 'no-debugger': 'error',
+ camelcase: 'error',
+
+ // Standard JS rules
+ semi: ['error', 'never'],
+ quotes: ['error', 'single', { avoidEscape: true }],
+ 'comma-dangle': ['error', 'always-multiline'],
+ 'keyword-spacing': ['error', { before: true, after: true }],
+ 'space-infix-ops': 'error',
+ 'eol-last': ['error', 'always'],
+ 'no-trailing-spaces': 'error',
+ 'object-curly-spacing': ['error', 'always'],
+ 'array-bracket-spacing': ['error', 'never'],
+ 'computed-property-spacing': ['error', 'never'],
+ 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }],
+ indent: ['error', 2],
+ 'linebreak-style': ['error', 'unix'],
+ eqeqeq: ['error', 'always', { null: 'ignore' }],
+ 'new-cap': ['error', { newIsCap: true, capIsNew: false, properties: true }],
+ 'array-callback-return': ['error', { allowImplicit: false, checkForEach: false }],
+
+ // React rules
+ 'react/react-in-jsx-scope': 'off',
+ 'react/no-unescaped-entities': 'error',
+ 'react-hooks/rules-of-hooks': 'error',
+ 'react-hooks/exhaustive-deps': 'warn',
+
+ // Import rules
+ 'import-x/export': 'error',
+ 'import-x/no-deprecated': 'error',
+ 'import-x/no-empty-named-blocks': 'error',
+ 'import-x/no-extraneous-dependencies': 'error',
+ 'import-x/no-mutable-exports': 'error',
+ 'import-x/no-named-as-default': 'error',
+ 'import-x/no-named-as-default-member': 'error',
+ 'import-x/no-amd': 'error',
+ 'import-x/no-commonjs': 'error',
+ 'import-x/no-nodejs-modules': 'off',
+ 'import-x/default': 'error',
+ 'import-x/named': 'error',
+ 'import-x/namespace': 'error',
+ 'import-x/no-absolute-path': 'error',
+ 'import-x/no-cycle': 'error',
+ 'import-x/no-dynamic-require': 'error',
+ 'import-x/no-internal-modules': 'off',
+ 'import-x/no-relative-packages': 'error',
+ 'import-x/no-self-import': 'error',
+ 'import-x/no-unresolved': ['error', { ignore: ['react'] }],
+ 'import-x/no-useless-path-segments': 'error',
+ 'import-x/no-webpack-loader-syntax': 'error',
+ 'import-x/consistent-type-specifier-style': 'error',
+ 'import-x/exports-last': 'off',
+ 'import-x/extensions': ['error', 'never', { json: 'always' }],
+ 'import-x/first': 'error',
+ 'import-x/group-exports': 'off',
+ 'import-x/newline-after-import': 'error',
+ 'import-x/no-anonymous-default-export': 'off',
+ 'import-x/no-default-export': 'off',
+ 'import-x/no-duplicates': 'error',
+ 'import-x/no-named-default': 'error',
+ 'import-x/no-namespace': 'error',
+ 'import-x/no-unassigned-import': ['error', { allow: ['**/*.css'] }],
+ 'import-x/order': [
+ 'error',
+ {
+ groups: [
+ 'builtin',
+ 'external',
+ 'internal',
+ 'parent',
+ 'sibling',
+ 'index',
+ 'object',
+ 'type',
+ ],
+ 'newlines-between': 'always',
+ alphabetize: { order: 'asc', caseInsensitive: true },
+ distinctGroup: true,
+ },
+ ],
+ 'import-x/prefer-default-export': 'off',
+ 'import-x/no-unused-modules': 'error',
+ 'import-x/no-import-module-exports': 'error',
+ 'import-x/unambiguous': 'off',
+ 'import-x/no-relative-parent-imports': [
+ 'error',
+ { ignore: ['#[src,types,root,components,utils,assets]/*'] },
+ ],
+
+ // Promise rules
+ 'promise/catch-or-return': 'error',
+ 'promise/no-return-wrap': 'error',
+ 'promise/param-names': 'error',
+ 'promise/always-return': 'error',
+ 'promise/no-native': 'off',
+ 'promise/no-nesting': 'warn',
+ 'promise/no-promise-in-callback': 'warn',
+ 'promise/no-callback-in-promise': 'warn',
+ 'promise/avoid-new': 'warn',
+ 'promise/no-new-statics': 'error',
+ 'promise/no-return-in-finally': 'warn',
+ 'promise/valid-params': 'warn',
+ 'promise/prefer-await-to-callbacks': 'error',
+ 'promise/no-multiple-resolved': 'error',
+
+ // Security and other rules
+ 'no-catch-all/no-catch-all': 'error',
+ },
+ },
+
+ // TypeScript configs (applied after main config)
+ ...tseslint.configs.recommended,
+ ...tseslint.configs.strict,
+ ...tseslint.configs.stylistic,
+
+ // TypeScript type-checking configuration
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ ...tseslint.configs.recommendedTypeChecked,
+ ...tseslint.configs.strictTypeChecked,
+ ...tseslint.configs.stylisticTypeChecked,
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ rules: {
+ '@typescript-eslint/consistent-type-imports': 'error',
+ '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
+ 'no-void': ['error', { allowAsStatement: true }],
+
+ // Disable empty function rule - legitimate use in React contexts
+ '@typescript-eslint/no-empty-function': 'off',
+
+ // Configure no-unused-expressions to allow logical AND and ternary patterns
+ '@typescript-eslint/no-unused-expressions': [
+ 'error',
+ {
+ allowShortCircuit: true,
+ allowTernary: true,
+ },
+ ],
+ },
+ },
+
+ // JSON files configuration
+ {
+ files: ['**/*.json'],
+ plugins: {
+ json: jsonPlugin,
+ },
+ rules: {
+ // Disable TypeScript-specific rules for JSON files
+ '@typescript-eslint/no-unused-expressions': 'off',
+ // JSON-specific rules
+ 'json/*': 'error',
+ },
+ },
+
+ // CommonJS configuration files (e.g., postcss.config.cjs)
+ // Provides Node.js globals (module, require, etc.) that were previously
+ // included via eslint-config-standard in ESLint v8
+ {
+ files: ['**/*.cjs'],
+ languageOptions: {
+ globals: {
+ ...globals.node,
+ },
+ sourceType: 'commonjs',
+ },
+ },
+
+ // Prettier recommended config (should be last to override other formatting rules)
+ eslintPluginPrettierRecommended,
+)
diff --git a/lib/package.json b/lib/package.json
index 888159af..5eb4be30 100644
--- a/lib/package.json
+++ b/lib/package.json
@@ -26,7 +26,7 @@
"scripts": {
"build": "rollup -c",
"start": "rollup -c -w",
- "test:lint:eslint": "eslint --ext .ts,.tsx,.js,.jsx,.cjs,.mjs,.json,.yml,.yaml --max-warnings 0 .",
+ "test:lint:eslint": "eslint --max-warnings 0 .",
"lint": "npm run test:lint:eslint",
"lintfix": "npm run test:lint:eslint -- --fix",
"test:component": "cypress run --component --browser electron",
@@ -44,6 +44,7 @@
"license": "GPL-3.0-only",
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
+ "@eslint/js": "^9.36.0",
"@rollup/plugin-alias": "^6.0.0",
"@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-node-resolve": "^16.0.3",
@@ -56,19 +57,15 @@
"@types/leaflet.markercluster": "^1.5.5",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.0.5",
- "@typescript-eslint/eslint-plugin": "^5.62.0",
- "@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^3.0.5",
"cypress": "^15.7.1",
"daisyui": "^5.5.5",
- "eslint": "^8.24.0",
+ "eslint": "^9.36.0",
"eslint-config-prettier": "^10.1.8",
- "eslint-config-standard": "^17.1.0",
"eslint-import-resolver-typescript": "^4.4.4",
- "eslint-plugin-import": "^2.31.0",
+ "eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-json": "^3.1.0",
- "eslint-plugin-n": "^17.23.1",
"eslint-plugin-no-catch-all": "^1.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^7.2.1",
@@ -76,7 +73,7 @@
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"eslint-plugin-security": "^3.0.1",
- "eslint-plugin-yml": "^1.14.0",
+ "globals": "^16.3.0",
"happy-dom": "^20.0.11",
"postcss": "^8.4.21",
"prettier": "^3.7.4",
@@ -91,6 +88,7 @@
"typedoc-plugin-coverage": "^3.4.1",
"typedoc-plugin-missing-exports": "^3.1.0",
"typescript": "^5.9.3",
+ "typescript-eslint": "^8.9.0",
"vite": "^7.2.7",
"vite-plugin-svgr": "^4.3.0",
"vitest": "^3.0.5"
diff --git a/lib/postcss.config.cjs b/lib/postcss.config.cjs
index 6bb9c83c..4d0b5bd1 100644
--- a/lib/postcss.config.cjs
+++ b/lib/postcss.config.cjs
@@ -1,4 +1,4 @@
-// eslint-disable-next-line import/no-commonjs
+// eslint-disable-next-line import-x/no-commonjs
module.exports = {
plugins: {
'@tailwindcss/postcss': {},
diff --git a/lib/setupTest.ts b/lib/setupTest.ts
index 3ed9ed03..0f35e917 100644
--- a/lib/setupTest.ts
+++ b/lib/setupTest.ts
@@ -1,2 +1,2 @@
-// eslint-disable-next-line import/no-unassigned-import
+// eslint-disable-next-line import-x/no-unassigned-import
import '@testing-library/jest-dom'
diff --git a/lib/src/Components/AppShell/NavBar.tsx b/lib/src/Components/AppShell/NavBar.tsx
index 51f521b9..17eecc01 100644
--- a/lib/src/Components/AppShell/NavBar.tsx
+++ b/lib/src/Components/AppShell/NavBar.tsx
@@ -31,7 +31,9 @@ export default function NavBar({ appName }: { appName: string }) {
className='tw:btn tw:btn-square tw:btn-ghost tw:ml-3'
aria-controls='#sidenav'
aria-haspopup='true'
- onClick={() => toggleSidebar()}
+ onClick={() => {
+ toggleSidebar()
+ }}
>
@@ -50,7 +52,9 @@ export default function NavBar({ appName }: { appName: string }) {
diff --git a/lib/src/Components/AppShell/SideBar.tsx b/lib/src/Components/AppShell/SideBar.tsx
index 4c7d6799..1e8a1747 100644
--- a/lib/src/Components/AppShell/SideBar.tsx
+++ b/lib/src/Components/AppShell/SideBar.tsx
@@ -6,7 +6,7 @@ import SidebarSubmenu from './SidebarSubmenu'
export interface Route {
path: string
- icon: JSX.Element
+ icon: React.JSX.Element
name: string
submenu?: Route[]
blank?: boolean
@@ -57,7 +57,9 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
to={`${route.path}${params && '?' + params.toString()}`}
className={({ isActive }) =>
- `${isActive ? 'tw:font-semibold tw:bg-base-200 tw:rounded-none!' : 'tw:font-normal tw:rounded-none!'}`
+ isActive
+ ? 'tw:font-semibold tw:bg-base-200 tw:rounded-none!'
+ : 'tw:font-normal tw:rounded-none!'
}
onClick={() => {
if (screen.width < 640 && !appState.sideBarSlim) toggleSidebarOpen()
@@ -70,7 +72,7 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
{route.icon}
{route.name}
@@ -119,7 +121,7 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
>
{route.icon}
{route.name}
@@ -143,7 +145,9 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
'tw:w-5 tw:h-5 tw:mb-4 tw:mr-5 tw:mt-2 tw:cursor-pointer tw:float-right tw:delay-400 tw:duration-500 tw:transition-all ' +
(!appState.sideBarSlim ? 'tw:rotate-180' : '')
}
- onClick={() => toggleSidebarSlim()}
+ onClick={() => {
+ toggleSidebarSlim()
+ }}
/>
diff --git a/lib/src/Components/AppShell/SidebarSubmenu.tsx b/lib/src/Components/AppShell/SidebarSubmenu.tsx
index 347d1323..a5f39ea0 100644
--- a/lib/src/Components/AppShell/SidebarSubmenu.tsx
+++ b/lib/src/Components/AppShell/SidebarSubmenu.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/prefer-find */
import ChevronDownIcon from '@heroicons/react/24/outline/ChevronDownIcon'
import { useEffect, useState } from 'react'
import { Link, useLocation } from 'react-router-dom'
@@ -10,7 +11,7 @@ function SidebarSubmenu({
icon,
}: {
path: string
- icon: JSX.Element
+ icon: React.JSX.Element
name: string
submenu?: Route[]
}) {
@@ -31,7 +32,12 @@ function SidebarSubmenu({
return (
{/** Route header */}
-
setIsExpanded(!isExpanded)}>
+
{
+ setIsExpanded(!isExpanded)
+ }}
+ >
{icon}
{name}
setEmail(e.target.value)}
+ onChange={(e) => {
+ setEmail(e.target.value)
+ }}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
setPassword(e.target.value)}
+ onChange={(e) => {
+ setPassword(e.target.value)
+ }}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
diff --git a/lib/src/Components/Auth/RequestPasswordPage.tsx b/lib/src/Components/Auth/RequestPasswordPage.tsx
index dcfb416f..713516c1 100644
--- a/lib/src/Components/Auth/RequestPasswordPage.tsx
+++ b/lib/src/Components/Auth/RequestPasswordPage.tsx
@@ -28,7 +28,7 @@ export function RequestPasswordPage({ resetUrl }: { resetUrl: string }) {
},
error: {
render({ data }) {
- return `${data as string}`
+ return data as string
},
},
pending: 'sending email ...',
@@ -42,7 +42,9 @@ export function RequestPasswordPage({ resetUrl }: { resetUrl: string }) {
type='email'
placeholder='E-Mail'
value={email}
- onChange={(e) => setEmail(e.target.value)}
+ onChange={(e) => {
+ setEmail(e.target.value)
+ }}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
diff --git a/lib/src/Components/Auth/SetNewPasswordPage.tsx b/lib/src/Components/Auth/SetNewPasswordPage.tsx
index c8f59591..fc00649c 100644
--- a/lib/src/Components/Auth/SetNewPasswordPage.tsx
+++ b/lib/src/Components/Auth/SetNewPasswordPage.tsx
@@ -28,7 +28,7 @@ export function SetNewPasswordPage() {
},
error: {
render({ data }) {
- return `${data as string}`
+ return data as string
},
},
pending: 'setting password ...',
@@ -41,7 +41,9 @@ export function SetNewPasswordPage() {
setPassword(e.target.value)}
+ onChange={(e) => {
+ setPassword(e.target.value)
+ }}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
diff --git a/lib/src/Components/Auth/SignupPage.tsx b/lib/src/Components/Auth/SignupPage.tsx
index 1eb51ea9..13c0089f 100644
--- a/lib/src/Components/Auth/SignupPage.tsx
+++ b/lib/src/Components/Auth/SignupPage.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify'
@@ -32,7 +33,7 @@ export function SignupPage() {
},
error: {
render({ data }) {
- return `${data as string}`
+ return data as string
},
autoClose: 10000,
},
@@ -61,20 +62,26 @@ export function SignupPage() {
type='text'
placeholder='Name'
value={userName}
- onChange={(e) => setUserName(e.target.value)}
+ onChange={(e) => {
+ setUserName(e.target.value)
+ }}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
setEmail(e.target.value)}
+ onChange={(e) => {
+ setEmail(e.target.value)
+ }}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
setPassword(e.target.value)}
+ onChange={(e) => {
+ setPassword(e.target.value)
+ }}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
diff --git a/lib/src/Components/Auth/useAuth.tsx b/lib/src/Components/Auth/useAuth.tsx
index 413e88f5..fe332d80 100644
--- a/lib/src/Components/Auth/useAuth.tsx
+++ b/lib/src/Components/Auth/useAuth.tsx
@@ -67,7 +67,7 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
return undefined
}
// eslint-disable-next-line no-catch-all/no-catch-all
- } catch (error) {
+ } catch {
setLoading(false)
return undefined
} finally {
@@ -135,7 +135,8 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
setLoading(true)
try {
await userApi.requestPasswordReset(email, resetUrl)
- return setLoading(false)
+ setLoading(false)
+ return
} catch (error) {
setLoading(false)
throw error
@@ -146,7 +147,8 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
setLoading(true)
try {
await userApi.passwordReset(token, newPassword)
- return setLoading(false)
+ setLoading(false)
+ return
} catch (error) {
setLoading(false)
throw error
diff --git a/lib/src/Components/Gaming/Quests.tsx b/lib/src/Components/Gaming/Quests.tsx
index 944a2633..35d512b6 100644
--- a/lib/src/Components/Gaming/Quests.tsx
+++ b/lib/src/Components/Gaming/Quests.tsx
@@ -44,7 +44,9 @@ export function Quests() {
diff --git a/lib/src/Components/Gaming/hooks/useQuests.tsx b/lib/src/Components/Gaming/hooks/useQuests.tsx
index 1b3cc1d4..6539a008 100644
--- a/lib/src/Components/Gaming/hooks/useQuests.tsx
+++ b/lib/src/Components/Gaming/hooks/useQuests.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/no-empty-function */
import { useCallback, useState, createContext, useContext } from 'react'
type UseQuestManagerResult = ReturnType
diff --git a/lib/src/Components/Input/Autocomplete.tsx b/lib/src/Components/Input/Autocomplete.tsx
index 2bb97d2c..ae9be9c7 100644
--- a/lib/src/Components/Input/Autocomplete.tsx
+++ b/lib/src/Components/Input/Autocomplete.tsx
@@ -85,7 +85,9 @@ export const Autocomplete = ({
ref={inputRef}
{...inputProps}
type='text'
- onChange={(e) => handleChange(e)}
+ onChange={(e) => {
+ handleChange(e)
+ }}
tabIndex='-1'
onKeyDown={handleKeyDown}
className='tw:border-none tw:focus:outline-none tw:focus:ring-0 tw:mt-5'
@@ -94,7 +96,12 @@ export const Autocomplete = ({
className={`tw:absolute tw:z-4000 ${filteredSuggestions.length > 0 && 'tw:bg-base-100 tw:rounded-xl tw:p-2'}`}
>
{filteredSuggestions.map((suggestion, index) => (
- handleSuggestionClick(suggestion)}>
+ {
+ handleSuggestionClick(suggestion)
+ }}
+ >
))}
diff --git a/lib/src/Components/Item/PopupView.tsx b/lib/src/Components/Item/PopupView.tsx
index 98640f38..c73ac6a4 100644
--- a/lib/src/Components/Item/PopupView.tsx
+++ b/lib/src/Components/Item/PopupView.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
+/* eslint-disable @typescript-eslint/restrict-plus-operands */
import { useContext, useMemo, useState } from 'react'
import { Marker, Tooltip } from 'react-leaflet'
diff --git a/lib/src/Components/Map/Subcomponents/AddButton.tsx b/lib/src/Components/Map/Subcomponents/AddButton.tsx
index ab0e744b..c1944827 100644
--- a/lib/src/Components/Map/Subcomponents/AddButton.tsx
+++ b/lib/src/Components/Map/Subcomponents/AddButton.tsx
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
+/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { MapPinIcon } from '@heroicons/react/24/solid'
import { useState } from 'react'
import SVG from 'react-inlinesvg'
@@ -89,7 +90,9 @@ export default function AddButton({
tabIndex={0}
className='tw:z-500 tw:border-0 tw:p-0 tw:w-10 tw:h-10 tw:cursor-pointer tw:rounded-full tw:mouse tw:drop-shadow-md tw:transition tw:ease-in tw:duration-200 tw:focus:outline-hidden tw:flex tw:items-center tw:justify-center'
style={{ backgroundColor: layer.menuColor || '#777' }}
- onClick={() => handleLayerClick(layer)}
+ onClick={() => {
+ handleLayerClick(layer)
+ }}
onTouchEnd={(e) => {
handleLayerClick(layer)
e.preventDefault()
diff --git a/lib/src/Components/Map/Subcomponents/Controls/FilterControl.tsx b/lib/src/Components/Map/Subcomponents/Controls/FilterControl.tsx
index e5c6c340..03a6f8ce 100644
--- a/lib/src/Components/Map/Subcomponents/Controls/FilterControl.tsx
+++ b/lib/src/Components/Map/Subcomponents/Controls/FilterControl.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable array-callback-return */
import FunnelIcon from '@heroicons/react/24/outline/FunnelIcon'
import { useEffect, useState } from 'react'
@@ -18,7 +19,9 @@ export function FilterControl() {
]
useEffect(() => {
- groupTypes.map((layer) => addVisibleGroupType(layer.value))
+ groupTypes.map((layer) => {
+ addVisibleGroupType(layer.value)
+ })
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
@@ -48,7 +51,9 @@ export function FilterControl() {
>
toggleVisibleGroupType(groupType.value)}
+ onChange={() => {
+ toggleVisibleGroupType(groupType.value)
+ }}
type='checkbox'
className='tw:checkbox tw:checkbox-xs tw:checkbox-success'
checked={isGroupTypeVisible(groupType.value)}
diff --git a/lib/src/Components/Map/Subcomponents/Controls/LayerControl.tsx b/lib/src/Components/Map/Subcomponents/Controls/LayerControl.tsx
index 83a1a29d..6e40bd07 100644
--- a/lib/src/Components/Map/Subcomponents/Controls/LayerControl.tsx
+++ b/lib/src/Components/Map/Subcomponents/Controls/LayerControl.tsx
@@ -36,7 +36,9 @@ export function LayerControl({ expandLayerControl = false }: { expandLayerContro
>
toggleVisibleLayer(layer)}
+ onChange={() => {
+ toggleVisibleLayer(layer)
+ }}
type='checkbox'
className='tw:checkbox tw:checkbox-xs tw:checkbox-success tw:text-white'
checked={isLayerVisible(layer)}
diff --git a/lib/src/Components/Map/Subcomponents/Controls/LocateControl.spec.tsx b/lib/src/Components/Map/Subcomponents/Controls/LocateControl.spec.tsx
index 8bfbfd5c..909a0238 100644
--- a/lib/src/Components/Map/Subcomponents/Controls/LocateControl.spec.tsx
+++ b/lib/src/Components/Map/Subcomponents/Controls/LocateControl.spec.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable camelcase */ // Directus database fields use snake_case
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
diff --git a/lib/src/Components/Map/Subcomponents/Controls/LocateControl.tsx b/lib/src/Components/Map/Subcomponents/Controls/LocateControl.tsx
index 536ee3e5..f0907f81 100644
--- a/lib/src/Components/Map/Subcomponents/Controls/LocateControl.tsx
+++ b/lib/src/Components/Map/Subcomponents/Controls/LocateControl.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable camelcase */ // Directus database fields use snake_case
+/* eslint-disable promise/always-return */
import { control } from 'leaflet'
import { useCallback, useEffect, useRef, useState } from 'react'
import SVG from 'react-inlinesvg'
@@ -15,7 +17,7 @@ import DialogModal from '#components/Templates/DialogModal'
import type { Item } from '#types/Item'
import type { LatLng } from 'leaflet'
-// eslint-disable-next-line import/no-unassigned-import
+// eslint-disable-next-line import-x/no-unassigned-import
import 'leaflet.locatecontrol'
// Type definitions for leaflet.locatecontrol
@@ -31,7 +33,7 @@ declare module 'leaflet' {
* React wrapper for leaflet.locatecontrol that provides user geolocation functionality
* @category Map Controls
*/
-export const LocateControl = (): JSX.Element => {
+export const LocateControl = (): React.JSX.Element => {
const map = useMap()
const myProfile = useMyProfile()
const updateItem = useUpdateItem()
@@ -206,7 +208,9 @@ export const LocateControl = (): JSX.Element => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
if (lc) lc.stop()
// Reset flag after a delay to allow future updates
- setTimeout(() => setHasUpdatedPosition(false), 5000)
+ setTimeout(() => {
+ setHasUpdatedPosition(false)
+ }, 5000)
} catch (error: unknown) {
if (error instanceof Error) {
toast.update(toastId, {
@@ -278,7 +282,9 @@ export const LocateControl = (): JSX.Element => {