From 0775fd859e46c437f40da3c69585a06eb0c366f3 Mon Sep 17 00:00:00 2001 From: Elias Meire Date: Fri, 27 Jun 2025 10:42:47 +0200 Subject: [PATCH] build: Update ESLint to v9 (#16639) --- .npmignore | 2 +- cypress/.eslintrc.js | 40 - cypress/augmentation.d.ts | 2 +- cypress/eslint.config.mjs | 39 + cypress/package.json | 2 +- package.json | 4 +- .../@n8n/ai-workflow-builder/.eslintrc.js | 18 - .../ai-workflow-builder/eslint.config.mjs | 11 + packages/@n8n/api-types/.eslintrc.js | 10 - packages/@n8n/api-types/eslint.config.mjs | 12 + packages/@n8n/api-types/package.json | 2 +- packages/@n8n/backend-common/.eslintrc.js | 10 - .../@n8n/backend-common/eslint.config.mjs | 25 + packages/@n8n/backend-common/package.json | 2 +- .../src/utils/is-object-literal.ts | 2 +- packages/@n8n/backend-test-utils/.eslintrc.js | 7 - .../@n8n/backend-test-utils/eslint.config.mjs | 10 + packages/@n8n/backend-test-utils/package.json | 2 +- packages/@n8n/benchmark/.eslintrc.js | 31 - packages/@n8n/benchmark/eslint.config.mjs | 22 + packages/@n8n/benchmark/package.json | 2 +- packages/@n8n/client-oauth2/.eslintrc.js | 17 - packages/@n8n/client-oauth2/eslint.config.mjs | 29 + packages/@n8n/codemirror-lang/.eslintrc.cjs | 12 - .../@n8n/codemirror-lang/eslint.config.mjs | 11 + packages/@n8n/codemirror-lang/package.json | 4 +- packages/@n8n/config/.eslintrc.js | 23 - packages/@n8n/config/eslint.config.mjs | 22 + packages/@n8n/config/package.json | 2 +- packages/@n8n/config/src/decorators.ts | 2 +- packages/@n8n/constants/.eslintrc.js | 10 - packages/@n8n/constants/eslint.config.mjs | 8 + packages/@n8n/constants/package.json | 2 +- packages/@n8n/db/.eslintrc.js | 18 - packages/@n8n/db/eslint.config.mjs | 38 + packages/@n8n/db/package.json | 2 +- .../@n8n/db/src/migrations/migration-types.ts | 2 +- packages/@n8n/decorators/.eslintrc.js | 10 - packages/@n8n/decorators/eslint.config.mjs | 28 + packages/@n8n/decorators/package.json | 2 +- packages/@n8n/decorators/src/redactable.ts | 2 +- packages/@n8n/di/.eslintrc.js | 10 - packages/@n8n/di/eslint.config.mjs | 13 + packages/@n8n/di/package.json | 2 +- .../di/src/__tests__/fixtures/service-a.ts | 2 +- .../di/src/__tests__/fixtures/service-b.ts | 2 +- packages/@n8n/di/src/di.ts | 4 +- packages/@n8n/eslint-config/base.js | 498 -- packages/@n8n/eslint-config/frontend.js | 102 - packages/@n8n/eslint-config/jest.config.js | 2 - packages/@n8n/eslint-config/local-rules.js | 465 -- .../@n8n/eslint-config/local-rules.test.js | 83 - packages/@n8n/eslint-config/node.js | 11 - packages/@n8n/eslint-config/package.json | 74 +- packages/@n8n/eslint-config/shared.js | 41 - .../@n8n/eslint-config/src/configs/base.ts | 423 ++ .../eslint-config/src/configs/frontend.ts | 101 + .../@n8n/eslint-config/src/configs/node.ts | 10 + packages/@n8n/eslint-config/src/plugin.ts | 30 + packages/@n8n/eslint-config/src/plugins.d.ts | 1 + .../@n8n/eslint-config/src/rules/index.ts | 28 + .../src/rules/misplaced-n8n-typeorm-import.ts | 24 + .../src/rules/no-dynamic-import-template.ts | 31 + .../no-interpolation-in-regular-string.ts | 31 + .../no-json-parse-json-stringify.test.ts | 34 + .../src/rules/no-json-parse-json-stringify.ts | 48 + .../src/rules/no-plain-errors.ts | 49 + .../src/rules/no-skipped-tests.ts | 57 + .../src/rules/no-type-unsafe-event-emitter.ts | 32 + .../src/rules/no-uncaught-json-parse.test.ts | 21 + .../src/rules/no-uncaught-json-parse.ts | 44 + .../src/rules/no-unneeded-backticks.ts | 35 + .../rules/no-untyped-config-class-field.ts | 25 + .../src/rules/no-unused-param-catch-clause.ts | 32 + .../src/rules/no-useless-catch-throw.test.ts | 34 + .../src/rules/no-useless-catch-throw.ts | 46 + packages/@n8n/eslint-config/src/utils/json.ts | 21 + packages/@n8n/eslint-config/tsconfig.json | 13 + packages/@n8n/eslint-config/vite.config.ts | 4 + packages/@n8n/extension-sdk/eslint.config.mjs | 14 + packages/@n8n/extension-sdk/package.json | 3 +- packages/@n8n/imap/.eslintrc.js | 16 - packages/@n8n/imap/eslint.config.mjs | 16 + packages/@n8n/json-schema-to-zod/.eslintrc.js | 21 - .../@n8n/json-schema-to-zod/eslint.config.mjs | 26 + packages/@n8n/json-schema-to-zod/package.json | 8 +- .../{postcjs.js => postcjs.cjs} | 0 .../{postesm.js => postesm.cjs} | 0 packages/@n8n/nodes-langchain/.eslintrc.js | 158 - .../@n8n/nodes-langchain/eslint.config.mjs | 164 + packages/@n8n/nodes-langchain/package.json | 2 +- .../utils/tests/tiktoken.test.ts | 1 + packages/@n8n/permissions/.eslintrc.js | 10 - packages/@n8n/permissions/eslint.config.mjs | 12 + packages/@n8n/task-runner/.eslintrc.js | 19 - packages/@n8n/task-runner/eslint.config.mjs | 28 + packages/@n8n/utils/.eslintrc.cjs | 10 - packages/@n8n/utils/eslint.config.mjs | 10 + packages/@n8n/utils/package.json | 10 +- packages/@n8n/utils/src/event-bus.ts | 4 +- .../@n8n/utils/src/search/sublimeSearch.ts | 2 +- packages/@n8n/vitest-config/frontend.d.ts | 6 - .../{frontend.mjs => frontend.ts} | 13 +- packages/@n8n/vitest-config/node.ts | 19 + packages/@n8n/vitest-config/package.json | 30 +- .../@n8n/vitest-config/tsconfig.build.json | 11 + packages/@n8n/vitest-config/tsconfig.json | 13 + packages/cli/.eslintrc.js | 75 - packages/cli/eslint.config.mjs | 102 + packages/cli/src/commands/execute.ts | 2 +- packages/cli/src/config/index.ts | 2 +- .../src/credentials/credentials.service.ts | 2 +- .../execute-error-workflow.ts | 2 +- .../execution-lifecycle-hooks.ts | 2 +- packages/cli/src/help.ts | 2 +- .../cli/src/load-nodes-and-credentials.ts | 2 +- .../src/services/cache/redis.cache-manager.ts | 6 +- packages/cli/src/services/folder.service.ts | 2 +- .../cli/src/services/project.service.ee.ts | 4 +- packages/cli/src/utlity.types.ts | 2 +- .../src/workflow-execute-additional-data.ts | 2 +- packages/cli/src/workflow-runner.ts | 2 +- .../cli/src/workflows/workflow.service.ts | 2 +- packages/core/.eslintrc.js | 24 - packages/core/eslint.config.mjs | 46 + .../supply-data-context.ts | 2 +- .../utils/get-input-connection-data.ts | 2 +- .../extensions/insights/eslint.config.mjs | 35 + packages/extensions/insights/package.json | 3 +- packages/frontend/@n8n/chat/.eslintignore | 2 - packages/frontend/@n8n/chat/.eslintrc.cjs | 10 - packages/frontend/@n8n/chat/eslint.config.mjs | 17 + packages/frontend/@n8n/chat/package.json | 10 +- .../@n8n/chat/src/__stories__/App.stories.ts | 2 +- .../frontend/@n8n/chat/src/__tests__/setup.ts | 1 - .../frontend/@n8n/chat/src/utils/event-bus.ts | 2 +- .../frontend/@n8n/composables/.eslintrc.cjs | 10 - .../@n8n/composables/eslint.config.mjs | 7 + .../frontend/@n8n/composables/package.json | 10 +- .../frontend/@n8n/design-system/.eslintrc.js | 48 - .../@n8n/design-system/eslint.config.mjs | 52 + .../frontend/@n8n/design-system/package.json | 13 +- .../{postcss.config.js => postcss.config.cjs} | 0 .../components/N8nIconPicker/IconPicker.vue | 2 +- packages/frontend/@n8n/i18n/.eslintrc.cjs | 10 - packages/frontend/@n8n/i18n/eslint.config.mjs | 13 + packages/frontend/@n8n/i18n/package.json | 10 +- .../@n8n/rest-api-client/.eslintrc.cjs | 10 - .../@n8n/rest-api-client/eslint.config.mjs | 15 + .../@n8n/rest-api-client/package.json | 10 +- packages/frontend/@n8n/stores/.eslintrc.cjs | 10 - .../frontend/@n8n/stores/eslint.config.mjs | 11 + packages/frontend/@n8n/stores/package.json | 10 +- packages/frontend/editor-ui/.eslintrc.js | 42 - packages/frontend/editor-ui/.npmignore | 2 +- packages/frontend/editor-ui/eslint.config.mjs | 58 + packages/frontend/editor-ui/package.json | 15 +- .../{postcss.config.js => postcss.config.cjs} | 0 .../editor-ui/src/components/ApiKeyScopes.vue | 2 +- .../frontend/editor-ui/tests/e2e/.eslintrc.js | 8 - packages/node-dev/.eslintrc.js | 16 - packages/node-dev/eslint.config.mjs | 12 + packages/node-dev/package.json | 4 +- packages/nodes-base/.eslintrc.js | 160 - packages/nodes-base/eslint.config.mjs | 177 + .../nodes/Microsoft/Sql/GenericFunctions.ts | 2 +- .../nodes-base/nodes/Onfleet/Onfleet.node.ts | 2 +- .../nodes/Postgres/v1/genericFunctions.ts | 6 +- packages/workflow/.eslintrc.js | 26 - packages/workflow/eslint.config.mjs | 45 + packages/workflow/package.json | 4 +- .../src/extensions/expression-extension.ts | 8 +- .../workflow/src/extensions/extensions.ts | 2 +- patches/eslint-plugin-n8n-local-rules.patch | 13 - pnpm-lock.yaml | 4683 ++++++++++------- pnpm-workspace.yaml | 25 +- 176 files changed, 5417 insertions(+), 4111 deletions(-) delete mode 100644 cypress/.eslintrc.js create mode 100644 cypress/eslint.config.mjs delete mode 100644 packages/@n8n/ai-workflow-builder/.eslintrc.js create mode 100644 packages/@n8n/ai-workflow-builder/eslint.config.mjs delete mode 100644 packages/@n8n/api-types/.eslintrc.js create mode 100644 packages/@n8n/api-types/eslint.config.mjs delete mode 100644 packages/@n8n/backend-common/.eslintrc.js create mode 100644 packages/@n8n/backend-common/eslint.config.mjs delete mode 100644 packages/@n8n/backend-test-utils/.eslintrc.js create mode 100644 packages/@n8n/backend-test-utils/eslint.config.mjs delete mode 100644 packages/@n8n/benchmark/.eslintrc.js create mode 100644 packages/@n8n/benchmark/eslint.config.mjs delete mode 100644 packages/@n8n/client-oauth2/.eslintrc.js create mode 100644 packages/@n8n/client-oauth2/eslint.config.mjs delete mode 100644 packages/@n8n/codemirror-lang/.eslintrc.cjs create mode 100644 packages/@n8n/codemirror-lang/eslint.config.mjs delete mode 100644 packages/@n8n/config/.eslintrc.js create mode 100644 packages/@n8n/config/eslint.config.mjs delete mode 100644 packages/@n8n/constants/.eslintrc.js create mode 100644 packages/@n8n/constants/eslint.config.mjs delete mode 100644 packages/@n8n/db/.eslintrc.js create mode 100644 packages/@n8n/db/eslint.config.mjs delete mode 100644 packages/@n8n/decorators/.eslintrc.js create mode 100644 packages/@n8n/decorators/eslint.config.mjs delete mode 100644 packages/@n8n/di/.eslintrc.js create mode 100644 packages/@n8n/di/eslint.config.mjs delete mode 100644 packages/@n8n/eslint-config/base.js delete mode 100644 packages/@n8n/eslint-config/frontend.js delete mode 100644 packages/@n8n/eslint-config/jest.config.js delete mode 100644 packages/@n8n/eslint-config/local-rules.js delete mode 100644 packages/@n8n/eslint-config/local-rules.test.js delete mode 100644 packages/@n8n/eslint-config/node.js delete mode 100644 packages/@n8n/eslint-config/shared.js create mode 100644 packages/@n8n/eslint-config/src/configs/base.ts create mode 100644 packages/@n8n/eslint-config/src/configs/frontend.ts create mode 100644 packages/@n8n/eslint-config/src/configs/node.ts create mode 100644 packages/@n8n/eslint-config/src/plugin.ts create mode 100644 packages/@n8n/eslint-config/src/plugins.d.ts create mode 100644 packages/@n8n/eslint-config/src/rules/index.ts create mode 100644 packages/@n8n/eslint-config/src/rules/misplaced-n8n-typeorm-import.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-dynamic-import-template.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-interpolation-in-regular-string.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-json-parse-json-stringify.test.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-json-parse-json-stringify.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-plain-errors.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-skipped-tests.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-type-unsafe-event-emitter.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-uncaught-json-parse.test.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-uncaught-json-parse.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-unneeded-backticks.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-untyped-config-class-field.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-unused-param-catch-clause.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-useless-catch-throw.test.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-useless-catch-throw.ts create mode 100644 packages/@n8n/eslint-config/src/utils/json.ts create mode 100644 packages/@n8n/eslint-config/tsconfig.json create mode 100644 packages/@n8n/eslint-config/vite.config.ts create mode 100644 packages/@n8n/extension-sdk/eslint.config.mjs delete mode 100644 packages/@n8n/imap/.eslintrc.js create mode 100644 packages/@n8n/imap/eslint.config.mjs delete mode 100644 packages/@n8n/json-schema-to-zod/.eslintrc.js create mode 100644 packages/@n8n/json-schema-to-zod/eslint.config.mjs rename packages/@n8n/json-schema-to-zod/{postcjs.js => postcjs.cjs} (100%) rename packages/@n8n/json-schema-to-zod/{postesm.js => postesm.cjs} (100%) delete mode 100644 packages/@n8n/nodes-langchain/.eslintrc.js create mode 100644 packages/@n8n/nodes-langchain/eslint.config.mjs delete mode 100644 packages/@n8n/permissions/.eslintrc.js create mode 100644 packages/@n8n/permissions/eslint.config.mjs delete mode 100644 packages/@n8n/task-runner/.eslintrc.js create mode 100644 packages/@n8n/task-runner/eslint.config.mjs delete mode 100644 packages/@n8n/utils/.eslintrc.cjs create mode 100644 packages/@n8n/utils/eslint.config.mjs delete mode 100644 packages/@n8n/vitest-config/frontend.d.ts rename packages/@n8n/vitest-config/{frontend.mjs => frontend.ts} (61%) create mode 100644 packages/@n8n/vitest-config/node.ts create mode 100644 packages/@n8n/vitest-config/tsconfig.build.json create mode 100644 packages/@n8n/vitest-config/tsconfig.json delete mode 100644 packages/cli/.eslintrc.js create mode 100644 packages/cli/eslint.config.mjs delete mode 100644 packages/core/.eslintrc.js create mode 100644 packages/core/eslint.config.mjs create mode 100644 packages/extensions/insights/eslint.config.mjs delete mode 100644 packages/frontend/@n8n/chat/.eslintignore delete mode 100644 packages/frontend/@n8n/chat/.eslintrc.cjs create mode 100644 packages/frontend/@n8n/chat/eslint.config.mjs delete mode 100644 packages/frontend/@n8n/composables/.eslintrc.cjs create mode 100644 packages/frontend/@n8n/composables/eslint.config.mjs delete mode 100644 packages/frontend/@n8n/design-system/.eslintrc.js create mode 100644 packages/frontend/@n8n/design-system/eslint.config.mjs rename packages/frontend/@n8n/design-system/{postcss.config.js => postcss.config.cjs} (100%) delete mode 100644 packages/frontend/@n8n/i18n/.eslintrc.cjs create mode 100644 packages/frontend/@n8n/i18n/eslint.config.mjs delete mode 100644 packages/frontend/@n8n/rest-api-client/.eslintrc.cjs create mode 100644 packages/frontend/@n8n/rest-api-client/eslint.config.mjs delete mode 100644 packages/frontend/@n8n/stores/.eslintrc.cjs create mode 100644 packages/frontend/@n8n/stores/eslint.config.mjs delete mode 100644 packages/frontend/editor-ui/.eslintrc.js create mode 100644 packages/frontend/editor-ui/eslint.config.mjs rename packages/frontend/editor-ui/{postcss.config.js => postcss.config.cjs} (100%) delete mode 100644 packages/frontend/editor-ui/tests/e2e/.eslintrc.js delete mode 100644 packages/node-dev/.eslintrc.js create mode 100644 packages/node-dev/eslint.config.mjs delete mode 100644 packages/nodes-base/.eslintrc.js create mode 100644 packages/nodes-base/eslint.config.mjs delete mode 100644 packages/workflow/.eslintrc.js create mode 100644 packages/workflow/eslint.config.mjs delete mode 100644 patches/eslint-plugin-n8n-local-rules.patch diff --git a/.npmignore b/.npmignore index 64227343cc..d811bf471a 100644 --- a/.npmignore +++ b/.npmignore @@ -21,7 +21,7 @@ yarn-error.log* *.sw* .editorconfig -.eslintrc.js +eslint.config.js tsconfig.json .turbo diff --git a/cypress/.eslintrc.js b/cypress/.eslintrc.js deleted file mode 100644 index 59f4e11ea7..0000000000 --- a/cypress/.eslintrc.js +++ /dev/null @@ -1,40 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/base', 'plugin:cypress/recommended'], - - ...sharedOptions(__dirname), - - plugins: ['cypress'], - - env: { - 'cypress/globals': true, - }, - - rules: { - // TODO: remove these rules - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unsafe-argument': 'off', - '@typescript-eslint/no-unsafe-assignment': 'off', - '@typescript-eslint/no-unsafe-call': 'off', - '@typescript-eslint/no-unsafe-member-access': 'off', - '@typescript-eslint/no-unsafe-return': 'off', - '@typescript-eslint/no-unused-expressions': 'off', - '@typescript-eslint/no-use-before-define': 'off', - '@typescript-eslint/promise-function-async': 'off', - 'n8n-local-rules/no-uncaught-json-parse': 'off', - 'cypress/no-assigning-return-values': 'warn', - 'cypress/no-unnecessary-waiting': 'warn', - 'cypress/unsafe-to-chain-command': 'warn', - 'import/no-extraneous-dependencies': [ - 'error', - { - devDependencies: ['**/cypress/**'], - optionalDependencies: false, - }, - ], - }, -}; diff --git a/cypress/augmentation.d.ts b/cypress/augmentation.d.ts index 334bc0e9f4..d3724f344b 100644 --- a/cypress/augmentation.d.ts +++ b/cypress/augmentation.d.ts @@ -1,4 +1,4 @@ declare module 'cypress-otp' { - // eslint-disable-next-line import/no-default-export + // eslint-disable-next-line import-x/no-default-export export default function generateOTPToken(secret: string): string; } diff --git a/cypress/eslint.config.mjs b/cypress/eslint.config.mjs new file mode 100644 index 0000000000..4b13be25ae --- /dev/null +++ b/cypress/eslint.config.mjs @@ -0,0 +1,39 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; +import cypressPlugin from 'eslint-plugin-cypress/flat'; + +export default defineConfig( + globalIgnores(['scripts/**/*.js']), + baseConfig, + cypressPlugin.configs.recommended, + { + rules: { + // TODO: Remove this + 'no-useless-escape': 'warn', + 'import-x/order': 'warn', + 'import-x/no-extraneous-dependencies': [ + 'error', + { + devDependencies: ['**/cypress/**'], + optionalDependencies: false, + }, + ], + '@typescript-eslint/naming-convention': 'warn', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + '@typescript-eslint/no-unsafe-assignment': 'warn', + '@typescript-eslint/no-unsafe-call': 'warn', + '@typescript-eslint/no-unsafe-member-access': 'warn', + '@typescript-eslint/no-unsafe-return': 'warn', + '@typescript-eslint/no-unused-expressions': 'warn', + '@typescript-eslint/no-use-before-define': 'warn', + '@typescript-eslint/promise-function-async': 'warn', + '@typescript-eslint/prefer-nullish-coalescing': 'warn', + '@typescript-eslint/unbound-method': 'warn', + 'cypress/no-assigning-return-values': 'warn', + 'cypress/no-unnecessary-waiting': 'warn', + 'cypress/unsafe-to-chain-command': 'warn', + 'n8n-local-rules/no-uncaught-json-parse': 'warn', + }, + }, +); diff --git a/cypress/package.json b/cypress/package.json index 473c43e2b4..e88f648a20 100644 --- a/cypress/package.json +++ b/cypress/package.json @@ -19,7 +19,7 @@ "@cypress/grep": "^4.1.0", "@n8n/api-types": "workspace:*", "@types/lodash": "catalog:", - "eslint-plugin-cypress": "^3.5.0", + "eslint-plugin-cypress": "^4.3.0", "n8n-workflow": "workspace:*" }, "dependencies": { diff --git a/package.json b/package.json index 6b24606538..e64f20dce5 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "babel-plugin-transform-import-meta": "^2.3.2", "bundlemon": "^3.1.0", "cross-env": "^7.0.3", + "eslint": "catalog:", "jest": "^29.6.2", "jest-environment-jsdom": "^29.6.2", "jest-expect-message": "^1.1.3", @@ -94,7 +95,7 @@ "tar-fs": "2.1.3", "tslib": "^2.6.2", "tsconfig-paths": "^4.2.0", - "typescript": "^5.8.2", + "typescript": "catalog:", "vue-tsc": "^2.2.8", "google-gax": "^4.3.7", "ws": ">=8.17.1", @@ -110,7 +111,6 @@ "@types/ws@8.18.1": "patches/@types__ws@8.18.1.patch", "@types/uuencode@0.0.3": "patches/@types__uuencode@0.0.3.patch", "vue-tsc@2.2.8": "patches/vue-tsc@2.2.8.patch", - "eslint-plugin-n8n-local-rules": "patches/eslint-plugin-n8n-local-rules.patch", "element-plus@2.4.3": "patches/element-plus@2.4.3.patch" } } diff --git a/packages/@n8n/ai-workflow-builder/.eslintrc.js b/packages/@n8n/ai-workflow-builder/.eslintrc.js deleted file mode 100644 index 26ff288e07..0000000000 --- a/packages/@n8n/ai-workflow-builder/.eslintrc.js +++ /dev/null @@ -1,18 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/node'], - - ...sharedOptions(__dirname), - - ignorePatterns: ['jest.config.js'], - - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - - complexity: 'error', - }, -}; diff --git a/packages/@n8n/ai-workflow-builder/eslint.config.mjs b/packages/@n8n/ai-workflow-builder/eslint.config.mjs new file mode 100644 index 0000000000..89d6033ead --- /dev/null +++ b/packages/@n8n/ai-workflow-builder/eslint.config.mjs @@ -0,0 +1,11 @@ +import { defineConfig } from 'eslint/config'; +import { nodeConfig } from '@n8n/eslint-config/node'; + +export default defineConfig(nodeConfig, { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + complexity: 'error', + '@typescript-eslint/require-await': 'warn', + '@typescript-eslint/naming-convention': 'warn', + }, +}); diff --git a/packages/@n8n/api-types/.eslintrc.js b/packages/@n8n/api-types/.eslintrc.js deleted file mode 100644 index 96638bbca8..0000000000 --- a/packages/@n8n/api-types/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** @type {import('@types/eslint').ESLint.ConfigData} */ -module.exports = { - extends: ['@n8n/eslint-config/base'], - ...sharedOptions(__dirname), - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - }, -}; diff --git a/packages/@n8n/api-types/eslint.config.mjs b/packages/@n8n/api-types/eslint.config.mjs new file mode 100644 index 0000000000..793f7700a5 --- /dev/null +++ b/packages/@n8n/api-types/eslint.config.mjs @@ -0,0 +1,12 @@ +import { defineConfig } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; + +export default defineConfig(baseConfig, { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + + // TODO: Remove this + '@typescript-eslint/naming-convention': 'warn', + '@typescript-eslint/no-empty-object-type': 'warn', + }, +}); diff --git a/packages/@n8n/api-types/package.json b/packages/@n8n/api-types/package.json index d830199981..957f1d6552 100644 --- a/packages/@n8n/api-types/package.json +++ b/packages/@n8n/api-types/package.json @@ -8,7 +8,7 @@ "build": "tsc -p tsconfig.build.json", "format": "biome format --write .", "format:check": "biome ci .", - "lint": "eslint .", + "lint": "eslint . --quiet", "lintfix": "eslint . --fix", "watch": "tsc -p tsconfig.build.json --watch", "test": "jest", diff --git a/packages/@n8n/backend-common/.eslintrc.js b/packages/@n8n/backend-common/.eslintrc.js deleted file mode 100644 index 96638bbca8..0000000000 --- a/packages/@n8n/backend-common/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** @type {import('@types/eslint').ESLint.ConfigData} */ -module.exports = { - extends: ['@n8n/eslint-config/base'], - ...sharedOptions(__dirname), - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - }, -}; diff --git a/packages/@n8n/backend-common/eslint.config.mjs b/packages/@n8n/backend-common/eslint.config.mjs new file mode 100644 index 0000000000..3d76570ea1 --- /dev/null +++ b/packages/@n8n/backend-common/eslint.config.mjs @@ -0,0 +1,25 @@ +import { defineConfig } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; + +export default defineConfig( + baseConfig, + { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + + // TODO: Remove this + '@typescript-eslint/naming-convention': 'warn', + '@typescript-eslint/no-wrapper-object-types': 'warn', + }, + }, + { + files: ['**/*.test.ts'], + rules: { + 'n8n-local-rules/no-uncaught-json-parse': 'warn', + '@typescript-eslint/no-unsafe-return': 'warn', + '@typescript-eslint/no-unsafe-assignment': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + '@typescript-eslint/unbound-method': 'warn', + }, + }, +); diff --git a/packages/@n8n/backend-common/package.json b/packages/@n8n/backend-common/package.json index 8db8645376..d501661ce0 100644 --- a/packages/@n8n/backend-common/package.json +++ b/packages/@n8n/backend-common/package.json @@ -8,7 +8,7 @@ "build": "tsc -p tsconfig.build.json", "format": "biome format --write .", "format:check": "biome ci .", - "lint": "eslint .", + "lint": "eslint . --quiet", "lintfix": "eslint . --fix", "watch": "tsc -p tsconfig.build.json --watch", "test": "jest", diff --git a/packages/@n8n/backend-common/src/utils/is-object-literal.ts b/packages/@n8n/backend-common/src/utils/is-object-literal.ts index c0d29c83dc..086831c694 100644 --- a/packages/@n8n/backend-common/src/utils/is-object-literal.ts +++ b/packages/@n8n/backend-common/src/utils/is-object-literal.ts @@ -12,7 +12,7 @@ export function isObjectLiteral(candidate: unknown): candidate is ObjectLiteral typeof candidate === 'object' && candidate !== null && !Array.isArray(candidate) && - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-restricted-types (Object.getPrototypeOf(candidate) as Object)?.constructor?.name === 'Object' ); } diff --git a/packages/@n8n/backend-test-utils/.eslintrc.js b/packages/@n8n/backend-test-utils/.eslintrc.js deleted file mode 100644 index 8c5b78c5da..0000000000 --- a/packages/@n8n/backend-test-utils/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** @type {import('@types/eslint').ESLint.ConfigData} */ -module.exports = { - extends: ['@n8n/eslint-config/base'], - ...sharedOptions(__dirname), -}; diff --git a/packages/@n8n/backend-test-utils/eslint.config.mjs b/packages/@n8n/backend-test-utils/eslint.config.mjs new file mode 100644 index 0000000000..9871ec1458 --- /dev/null +++ b/packages/@n8n/backend-test-utils/eslint.config.mjs @@ -0,0 +1,10 @@ +import { defineConfig } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; + +export default defineConfig(baseConfig, { + rules: { + // TODO: Remove this + '@typescript-eslint/require-await': 'warn', + '@typescript-eslint/naming-convention': 'warn', + }, +}); diff --git a/packages/@n8n/backend-test-utils/package.json b/packages/@n8n/backend-test-utils/package.json index 24f2d8169a..cf51f9c323 100644 --- a/packages/@n8n/backend-test-utils/package.json +++ b/packages/@n8n/backend-test-utils/package.json @@ -8,7 +8,7 @@ "build": "tsc -p tsconfig.build.json", "format": "biome format --write .", "format:check": "biome ci .", - "lint": "eslint .", + "lint": "eslint . --quiet", "lintfix": "eslint . --fix", "watch": "tsc -p tsconfig.build.json --watch", "test": "jest", diff --git a/packages/@n8n/benchmark/.eslintrc.js b/packages/@n8n/benchmark/.eslintrc.js deleted file mode 100644 index 83698e4feb..0000000000 --- a/packages/@n8n/benchmark/.eslintrc.js +++ /dev/null @@ -1,31 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/node'], - - ...sharedOptions(__dirname), - - parserOptions: { - project: './tsconfig.json', - }, - - ignorePatterns: ['scenarios/**'], - - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - 'n8n-local-rules/no-plain-errors': 'off', - complexity: 'error', - }, - - overrides: [ - { - files: ['./src/commands/*.ts'], - rules: { - 'import/no-default-export': 'off', - }, - }, - ], -}; diff --git a/packages/@n8n/benchmark/eslint.config.mjs b/packages/@n8n/benchmark/eslint.config.mjs new file mode 100644 index 0000000000..eefa64111e --- /dev/null +++ b/packages/@n8n/benchmark/eslint.config.mjs @@ -0,0 +1,22 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import { nodeConfig } from '@n8n/eslint-config/node'; + +export default defineConfig( + nodeConfig, + globalIgnores(['scenarios/**', 'scripts/**']), + { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + 'n8n-local-rules/no-plain-errors': 'off', + complexity: 'error', + '@typescript-eslint/naming-convention': 'warn', + 'no-empty': 'warn', + }, + }, + { + files: ['./src/commands/*.ts'], + rules: { + 'import-x/no-default-export': 'off', + }, + }, +); diff --git a/packages/@n8n/benchmark/package.json b/packages/@n8n/benchmark/package.json index f294462d08..05d3163903 100644 --- a/packages/@n8n/benchmark/package.json +++ b/packages/@n8n/benchmark/package.json @@ -7,7 +7,7 @@ "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json", "format": "biome format --write .", "format:check": "biome ci .", - "lint": "eslint .", + "lint": "eslint . --quiet", "lintfix": "eslint . --fix", "start": "./bin/n8n-benchmark", "test": "echo \"Error: no test specified\" && exit 1", diff --git a/packages/@n8n/client-oauth2/.eslintrc.js b/packages/@n8n/client-oauth2/.eslintrc.js deleted file mode 100644 index 89597cea23..0000000000 --- a/packages/@n8n/client-oauth2/.eslintrc.js +++ /dev/null @@ -1,17 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/base'], - - ...sharedOptions(__dirname), - - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - '@typescript-eslint/consistent-type-imports': 'error', - 'n8n-local-rules/no-plain-errors': 'off', - 'n8n-local-rules/no-uncaught-json-parse': 'off', - }, -}; diff --git a/packages/@n8n/client-oauth2/eslint.config.mjs b/packages/@n8n/client-oauth2/eslint.config.mjs new file mode 100644 index 0000000000..3ee90ff2a6 --- /dev/null +++ b/packages/@n8n/client-oauth2/eslint.config.mjs @@ -0,0 +1,29 @@ +import { defineConfig } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; + +export default defineConfig( + baseConfig, + { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + '@typescript-eslint/consistent-type-imports': 'error', + 'n8n-local-rules/no-plain-errors': 'off', + 'n8n-local-rules/no-uncaught-json-parse': 'off', + + // TODO: Remove this + '@typescript-eslint/naming-convention': 'warn', + '@typescript-eslint/require-await': 'warn', + }, + }, + { + files: ['**/*.test.ts'], + rules: { + // TODO: Remove this + 'id-denylist': 'warn', + '@typescript-eslint/no-unsafe-return': 'warn', + '@typescript-eslint/no-unsafe-call': 'warn', + '@typescript-eslint/no-unsafe-member-access': 'warn', + '@typescript-eslint/no-unsafe-assignment': 'warn', + }, + }, +); diff --git a/packages/@n8n/codemirror-lang/.eslintrc.cjs b/packages/@n8n/codemirror-lang/.eslintrc.cjs deleted file mode 100644 index 8d407e73db..0000000000 --- a/packages/@n8n/codemirror-lang/.eslintrc.cjs +++ /dev/null @@ -1,12 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/base'], - - ...sharedOptions(__dirname), - - ignorePatterns: ['src/expressions/grammar*.ts'], -}; diff --git a/packages/@n8n/codemirror-lang/eslint.config.mjs b/packages/@n8n/codemirror-lang/eslint.config.mjs new file mode 100644 index 0000000000..91af7c53f0 --- /dev/null +++ b/packages/@n8n/codemirror-lang/eslint.config.mjs @@ -0,0 +1,11 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; + +export default defineConfig(baseConfig, globalIgnores(['src/expressions/grammar*.ts']), { + rules: { + // TODO: Remove this + '@typescript-eslint/naming-convention': 'warn', + 'no-useless-escape': 'warn', + '@typescript-eslint/unbound-method': 'warn', + }, +}); diff --git a/packages/@n8n/codemirror-lang/package.json b/packages/@n8n/codemirror-lang/package.json index 371702c488..cddb07e93e 100644 --- a/packages/@n8n/codemirror-lang/package.json +++ b/packages/@n8n/codemirror-lang/package.json @@ -22,8 +22,8 @@ "generate": "pnpm generate:expressions:grammar && pnpm format", "build": "tsc -p tsconfig.build.json", "test": "jest", - "lint": "eslint . --ext .ts --quiet", - "lintfix": "eslint . --ext .ts --fix", + "lint": "eslint . --quiet", + "lintfix": "eslint . --fix", "format": "biome format --write src test", "format:check": "biome ci src test" }, diff --git a/packages/@n8n/config/.eslintrc.js b/packages/@n8n/config/.eslintrc.js deleted file mode 100644 index daa8004587..0000000000 --- a/packages/@n8n/config/.eslintrc.js +++ /dev/null @@ -1,23 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/node'], - - ...sharedOptions(__dirname), - - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - }, - - overrides: [ - { - files: ['**/*.config.ts'], - rules: { - 'n8n-local-rules/no-untyped-config-class-field': 'error', - }, - }, - ], -}; diff --git a/packages/@n8n/config/eslint.config.mjs b/packages/@n8n/config/eslint.config.mjs new file mode 100644 index 0000000000..4f15e57efb --- /dev/null +++ b/packages/@n8n/config/eslint.config.mjs @@ -0,0 +1,22 @@ +import { defineConfig } from 'eslint/config'; +import { nodeConfig } from '@n8n/eslint-config/node'; + +export default defineConfig( + nodeConfig, + { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + + // TODO: Remove this + '@typescript-eslint/naming-convention': 'warn', + '@typescript-eslint/no-unsafe-call': 'warn', + '@typescript-eslint/no-unsafe-function-type': 'warn', + }, + }, + { + files: ['**/*.config.ts'], + rules: { + 'n8n-local-rules/no-untyped-config-class-field': 'error', + }, + }, +); diff --git a/packages/@n8n/config/package.json b/packages/@n8n/config/package.json index 39b7e8aee6..13d5d8db3e 100644 --- a/packages/@n8n/config/package.json +++ b/packages/@n8n/config/package.json @@ -8,7 +8,7 @@ "build": "tsc -p tsconfig.build.json", "format": "biome format --write src test", "format:check": "biome ci src test", - "lint": "eslint .", + "lint": "eslint . --quiet", "lintfix": "eslint . --fix", "watch": "tsc -p tsconfig.build.json --watch", "test": "jest", diff --git a/packages/@n8n/config/src/decorators.ts b/packages/@n8n/config/src/decorators.ts index 4ee9b7e665..54af9e879a 100644 --- a/packages/@n8n/config/src/decorators.ts +++ b/packages/@n8n/config/src/decorators.ts @@ -3,7 +3,7 @@ import { Container, Service } from '@n8n/di'; import { readFileSync } from 'fs'; import { z } from 'zod'; -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types type Class = Function; type Constructable = new (rawValue: string) => T; type PropertyKey = string | symbol; diff --git a/packages/@n8n/constants/.eslintrc.js b/packages/@n8n/constants/.eslintrc.js deleted file mode 100644 index 96638bbca8..0000000000 --- a/packages/@n8n/constants/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** @type {import('@types/eslint').ESLint.ConfigData} */ -module.exports = { - extends: ['@n8n/eslint-config/base'], - ...sharedOptions(__dirname), - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - }, -}; diff --git a/packages/@n8n/constants/eslint.config.mjs b/packages/@n8n/constants/eslint.config.mjs new file mode 100644 index 0000000000..541ae772bb --- /dev/null +++ b/packages/@n8n/constants/eslint.config.mjs @@ -0,0 +1,8 @@ +import { defineConfig } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; + +export default defineConfig(baseConfig, { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + }, +}); diff --git a/packages/@n8n/constants/package.json b/packages/@n8n/constants/package.json index 23697cc678..e9815aef15 100644 --- a/packages/@n8n/constants/package.json +++ b/packages/@n8n/constants/package.json @@ -8,7 +8,7 @@ "build": "tsc -p tsconfig.build.json", "format": "biome format --write .", "format:check": "biome ci .", - "lint": "eslint .", + "lint": "eslint . --quiet", "lintfix": "eslint . --fix", "watch": "tsc -p tsconfig.build.json --watch" }, diff --git a/packages/@n8n/db/.eslintrc.js b/packages/@n8n/db/.eslintrc.js deleted file mode 100644 index f501d28070..0000000000 --- a/packages/@n8n/db/.eslintrc.js +++ /dev/null @@ -1,18 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** @type {import('@types/eslint').ESLint.ConfigData} */ -module.exports = { - extends: ['@n8n/eslint-config/base'], - ...sharedOptions(__dirname), - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - }, - overrides: [ - { - files: ['./src/migrations/**/*.ts'], - rules: { - 'unicorn/filename-case': 'off', - }, - }, - ], -}; diff --git a/packages/@n8n/db/eslint.config.mjs b/packages/@n8n/db/eslint.config.mjs new file mode 100644 index 0000000000..c110f39e38 --- /dev/null +++ b/packages/@n8n/db/eslint.config.mjs @@ -0,0 +1,38 @@ +import { defineConfig } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; + +export default defineConfig( + baseConfig, + { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + + // TODO: Remove this + '@typescript-eslint/naming-convention': 'warn', + '@typescript-eslint/no-unsafe-member-access': 'warn', + '@typescript-eslint/no-unsafe-assignment': 'warn', + '@typescript-eslint/prefer-nullish-coalescing': 'warn', + '@typescript-eslint/unbound-method': 'warn', + '@typescript-eslint/no-base-to-string': 'warn', + '@typescript-eslint/require-await': 'warn', + '@typescript-eslint/no-unsafe-call': 'warn', + '@typescript-eslint/no-unsafe-function-type': 'warn', + '@typescript-eslint/no-empty-object-type': 'warn', + '@typescript-eslint/no-restricted-types': 'warn', + 'no-useless-escape': 'warn', + 'no-empty': 'warn', + }, + }, + { + files: ['**/*.test.ts', '**/__tests__/**/*.ts'], + rules: { + '@typescript-eslint/no-unsafe-return': 'warn', + }, + }, + { + files: ['./src/migrations/**/*.ts'], + rules: { + 'unicorn/filename-case': 'off', + }, + }, +); diff --git a/packages/@n8n/db/package.json b/packages/@n8n/db/package.json index ee2bf205bf..a0202ab638 100644 --- a/packages/@n8n/db/package.json +++ b/packages/@n8n/db/package.json @@ -8,7 +8,7 @@ "build": "tsc -p tsconfig.build.json", "format": "biome format --write .", "format:check": "biome ci .", - "lint": "eslint .", + "lint": "eslint . --quiet", "lintfix": "eslint . --fix", "watch": "tsc -p tsconfig.build.json --watch", "test": "jest", diff --git a/packages/@n8n/db/src/migrations/migration-types.ts b/packages/@n8n/db/src/migrations/migration-types.ts index 7abd0b5aa4..fdd427b879 100644 --- a/packages/@n8n/db/src/migrations/migration-types.ts +++ b/packages/@n8n/db/src/migrations/migration-types.ts @@ -56,7 +56,7 @@ export interface IrreversibleMigration extends BaseMigration { down?: never; } -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export interface Migration extends Function { prototype: ReversibleMigration | IrreversibleMigration; } diff --git a/packages/@n8n/decorators/.eslintrc.js b/packages/@n8n/decorators/.eslintrc.js deleted file mode 100644 index 96638bbca8..0000000000 --- a/packages/@n8n/decorators/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** @type {import('@types/eslint').ESLint.ConfigData} */ -module.exports = { - extends: ['@n8n/eslint-config/base'], - ...sharedOptions(__dirname), - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - }, -}; diff --git a/packages/@n8n/decorators/eslint.config.mjs b/packages/@n8n/decorators/eslint.config.mjs new file mode 100644 index 0000000000..4fce051347 --- /dev/null +++ b/packages/@n8n/decorators/eslint.config.mjs @@ -0,0 +1,28 @@ +import { defineConfig } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; + +export default defineConfig( + baseConfig, + { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + + // TODO: Remove this + '@typescript-eslint/require-await': 'warn', + '@typescript-eslint/no-unsafe-call': 'warn', + '@typescript-eslint/naming-convention': 'warn', + '@typescript-eslint/no-base-to-string': 'warn', + '@typescript-eslint/no-unsafe-function-type': 'warn', + 'import-x/export': 'warn', + }, + }, + { + files: ['**/*.test.ts'], + rules: { + '@typescript-eslint/no-unused-expressions': 'warn', + '@typescript-eslint/no-unsafe-assignment': 'warn', + '@typescript-eslint/unbound-method': 'warn', + 'import-x/no-duplicates': 'warn', + }, + }, +); diff --git a/packages/@n8n/decorators/package.json b/packages/@n8n/decorators/package.json index 6b523a5412..7cb7bb828e 100644 --- a/packages/@n8n/decorators/package.json +++ b/packages/@n8n/decorators/package.json @@ -8,7 +8,7 @@ "build": "tsc -p tsconfig.build.json", "format": "biome format --write .", "format:check": "biome ci .", - "lint": "eslint .", + "lint": "eslint . --quiet", "lintfix": "eslint . --fix", "watch": "tsc -p tsconfig.build.json --watch", "test": "jest", diff --git a/packages/@n8n/decorators/src/redactable.ts b/packages/@n8n/decorators/src/redactable.ts index 5821024e4e..9ef47cf1f2 100644 --- a/packages/@n8n/decorators/src/redactable.ts +++ b/packages/@n8n/decorators/src/redactable.ts @@ -43,7 +43,7 @@ type FieldName = 'user' | 'inviter' | 'invitee'; export const Redactable = (fieldName: FieldName = 'user'): MethodDecorator => (_target, _propertyName, propertyDescriptor: PropertyDescriptor) => { - // eslint-disable-next-line @typescript-eslint/ban-types + // eslint-disable-next-line @typescript-eslint/no-restricted-types const originalMethod = propertyDescriptor.value as Function; type MethodArgs = Array<{ [fieldName: string]: UserLike }>; diff --git a/packages/@n8n/di/.eslintrc.js b/packages/@n8n/di/.eslintrc.js deleted file mode 100644 index 96638bbca8..0000000000 --- a/packages/@n8n/di/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** @type {import('@types/eslint').ESLint.ConfigData} */ -module.exports = { - extends: ['@n8n/eslint-config/base'], - ...sharedOptions(__dirname), - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - }, -}; diff --git a/packages/@n8n/di/eslint.config.mjs b/packages/@n8n/di/eslint.config.mjs new file mode 100644 index 0000000000..b95983cb30 --- /dev/null +++ b/packages/@n8n/di/eslint.config.mjs @@ -0,0 +1,13 @@ +import { defineConfig } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; + +export default defineConfig(baseConfig, { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + + // TODO: Remove this + '@typescript-eslint/naming-convention': 'warn', + '@typescript-eslint/no-unsafe-function-type': 'warn', + 'import-x/order': 'warn', + }, +}); diff --git a/packages/@n8n/di/package.json b/packages/@n8n/di/package.json index b8ca7e091d..8205ad726d 100644 --- a/packages/@n8n/di/package.json +++ b/packages/@n8n/di/package.json @@ -8,7 +8,7 @@ "build": "tsc -p tsconfig.build.json", "format": "biome format --write .", "format:check": "biome ci .", - "lint": "eslint .", + "lint": "eslint . --quiet", "lintfix": "eslint . --fix", "watch": "tsc -p tsconfig.build.json --watch", "test": "jest", diff --git a/packages/@n8n/di/src/__tests__/fixtures/service-a.ts b/packages/@n8n/di/src/__tests__/fixtures/service-a.ts index 387452db68..ea56dcfc67 100644 --- a/packages/@n8n/di/src/__tests__/fixtures/service-a.ts +++ b/packages/@n8n/di/src/__tests__/fixtures/service-a.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line import/no-cycle +// eslint-disable-next-line import-x/no-cycle import { ServiceB } from './service-b'; import { Service } from '../../di'; diff --git a/packages/@n8n/di/src/__tests__/fixtures/service-b.ts b/packages/@n8n/di/src/__tests__/fixtures/service-b.ts index d42a5f7fd2..0ffa2ff2f2 100644 --- a/packages/@n8n/di/src/__tests__/fixtures/service-b.ts +++ b/packages/@n8n/di/src/__tests__/fixtures/service-b.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line import/no-cycle +// eslint-disable-next-line import-x/no-cycle import { ServiceA } from './service-a'; import { Service } from '../../di'; diff --git a/packages/@n8n/di/src/di.ts b/packages/@n8n/di/src/di.ts index 61e940db5a..a6acc60ea5 100644 --- a/packages/@n8n/di/src/di.ts +++ b/packages/@n8n/di/src/di.ts @@ -30,9 +30,9 @@ const instances = new Map(); * @param options.factory Optional factory function to create instances of this class * @returns A class decorator to be applied to the target class */ -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export function Service(): Function; -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export function Service(options: Options): Function; export function Service({ factory }: Options = {}) { return function (target: Constructable) { diff --git a/packages/@n8n/eslint-config/base.js b/packages/@n8n/eslint-config/base.js deleted file mode 100644 index 69b46c1934..0000000000 --- a/packages/@n8n/eslint-config/base.js +++ /dev/null @@ -1,498 +0,0 @@ -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -const config = (module.exports = { - ignorePatterns: [ - 'node_modules/**', - 'dist/**', - 'tsup.config.ts', - // TODO: remove these - '*.js', - ], - - plugins: [ - /** - * Plugin with lint rules for import/export syntax - * https://github.com/import-js/eslint-plugin-import - */ - 'eslint-plugin-import', - - /** - * @typescript-eslint/eslint-plugin is required by eslint-config-airbnb-typescript - * See step 2: https://github.com/iamturns/eslint-config-airbnb-typescript#2-install-eslint-plugins - */ - '@typescript-eslint', - - /* - * Plugin to allow specifying local ESLint rules. - * https://github.com/ivov/eslint-plugin-n8n-local-rules - */ - 'eslint-plugin-n8n-local-rules', - - /** https://github.com/sweepline/eslint-plugin-unused-imports */ - 'unused-imports', - - /** https://github.com/sindresorhus/eslint-plugin-unicorn */ - 'eslint-plugin-unicorn', - - /** https://github.com/wix-incubator/eslint-plugin-lodash */ - 'eslint-plugin-lodash', - ], - - extends: [ - /** - * Config for typescript-eslint recommended ruleset (without type checking) - * - * https://github.com/typescript-eslint/typescript-eslint/blob/1c1b572c3000d72cfe665b7afbada0ec415e7855/packages/eslint-plugin/src/configs/recommended.ts - */ - 'plugin:@typescript-eslint/recommended', - - /** - * Config for typescript-eslint recommended ruleset (with type checking) - * - * https://github.com/typescript-eslint/typescript-eslint/blob/1c1b572c3000d72cfe665b7afbada0ec415e7855/packages/eslint-plugin/src/configs/recommended-requiring-type-checking.ts - */ - 'plugin:@typescript-eslint/recommended-requiring-type-checking', - - /** - * Config for Airbnb style guide for TS, /base to remove React rules - * - * https://github.com/iamturns/eslint-config-airbnb-typescript - * https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb-base/rules - */ - 'eslint-config-airbnb-typescript/base', - - /** - * Config to disable ESLint rules covered by Prettier - * - * https://github.com/prettier/eslint-config-prettier - */ - 'eslint-config-prettier', - ], - - rules: { - // ****************************************************************** - // additions to base ruleset - // ****************************************************************** - - // ---------------------------------- - // ESLint - // ---------------------------------- - - /** - * https://eslint.org/docs/rules/id-denylist - */ - 'id-denylist': [ - 'error', - 'err', - 'cb', - 'callback', - 'any', - 'Number', - 'number', - 'String', - 'string', - 'Boolean', - 'boolean', - 'Undefined', - 'undefined', - ], - - /** - * https://eslint.org/docs/latest/rules/no-void - */ - 'no-void': ['error', { allowAsStatement: true }], - - /** - * https://eslint.org/docs/latest/rules/indent - * - * Delegated to Prettier. - */ - indent: 'off', - - /** - * https://eslint.org/docs/latest/rules/no-constant-binary-expression - */ - 'no-constant-binary-expression': 'error', - - /** - * https://eslint.org/docs/latest/rules/sort-imports - */ - 'sort-imports': 'off', // @TECH_DEBT: Enable, prefs to be decided - N8N-5821 - - // ---------------------------------- - // @typescript-eslint - // ---------------------------------- - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/array-type.md - */ - '@typescript-eslint/array-type': ['error', { default: 'array-simple' }], - - /** https://typescript-eslint.io/rules/await-thenable/ */ - '@typescript-eslint/await-thenable': 'error', - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-ts-comment.md - */ - '@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': true }], - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-types.md - */ - '@typescript-eslint/ban-types': [ - 'error', - { - types: { - Object: { - message: 'Use object instead', - fixWith: 'object', - }, - String: { - message: 'Use string instead', - fixWith: 'string', - }, - Boolean: { - message: 'Use boolean instead', - fixWith: 'boolean', - }, - Number: { - message: 'Use number instead', - fixWith: 'number', - }, - Symbol: { - message: 'Use symbol instead', - fixWith: 'symbol', - }, - Function: { - message: [ - 'The `Function` type accepts any function-like value.', - 'It provides no type safety when calling the function, which can be a common source of bugs.', - 'It also accepts things like class declarations, which will throw at runtime as they will not be called with `new`.', - 'If you are expecting the function to accept certain arguments, you should explicitly define the function shape.', - ].join('\n'), - }, - }, - extendDefaults: false, - }, - ], - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-assertions.md - */ - '@typescript-eslint/consistent-type-assertions': 'error', - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-imports.md - */ - '@typescript-eslint/consistent-type-imports': 'error', - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-delimiter-style.md - */ - '@typescript-eslint/member-delimiter-style': [ - 'error', - { - multiline: { - delimiter: 'semi', - requireLast: true, - }, - singleline: { - delimiter: 'semi', - requireLast: false, - }, - }, - ], - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/naming-convention.md - */ - '@typescript-eslint/naming-convention': [ - 'error', - { - selector: 'default', - format: ['camelCase'], - }, - { - selector: 'variable', - format: ['camelCase', 'snake_case', 'UPPER_CASE', 'PascalCase'], - leadingUnderscore: 'allowSingleOrDouble', - trailingUnderscore: 'allowSingleOrDouble', - }, - { - selector: 'property', - format: ['camelCase', 'snake_case', 'UPPER_CASE'], - leadingUnderscore: 'allowSingleOrDouble', - trailingUnderscore: 'allowSingleOrDouble', - }, - { - selector: 'typeLike', - format: ['PascalCase'], - }, - { - selector: ['method', 'function', 'parameter'], - format: ['camelCase'], - leadingUnderscore: 'allowSingleOrDouble', - }, - ], - - /** - * https://github.com/import-js/eslint-plugin-import/blob/HEAD/docs/rules/no-duplicates.md - */ - 'import/no-duplicates': 'error', - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-invalid-void-type.md - */ - '@typescript-eslint/no-invalid-void-type': 'error', - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-misused-promises.md - */ - '@typescript-eslint/no-misused-promises': ['error', { checksVoidReturn: false }], - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/v4.30.0/packages/eslint-plugin/docs/rules/no-floating-promises.md - */ - '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }], - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/v4.33.0/packages/eslint-plugin/docs/rules/no-namespace.md - */ - '@typescript-eslint/no-namespace': 'off', - - /** - * https://eslint.org/docs/1.0.0/rules/no-throw-literal - */ - '@typescript-eslint/no-throw-literal': 'error', - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-boolean-literal-compare.md - */ - '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error', - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md - */ - '@typescript-eslint/no-unnecessary-qualifier': 'error', - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-expressions.md - */ - '@typescript-eslint/no-unused-expressions': 'error', - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md - */ - '@typescript-eslint/prefer-nullish-coalescing': 'error', - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-optional-chain.md - */ - '@typescript-eslint/prefer-optional-chain': 'error', - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/promise-function-async.md - */ - '@typescript-eslint/promise-function-async': 'error', - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/triple-slash-reference.md - */ - '@typescript-eslint/triple-slash-reference': 'off', // @TECH_DEBT: Enable, disallowing in all cases - N8N-5820 - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/naming-convention.md - */ - '@typescript-eslint/naming-convention': [ - 'error', - { - selector: 'import', - format: ['camelCase', 'PascalCase'], - }, - ], - - /** - * https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/return-await.md - */ - '@typescript-eslint/return-await': ['error', 'always'], - - /** - * https://typescript-eslint.io/rules/explicit-member-accessibility/ - */ - '@typescript-eslint/explicit-member-accessibility': ['error', { accessibility: 'no-public' }], - - // ---------------------------------- - // eslint-plugin-import - // ---------------------------------- - - /** - * https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-cycle.md - */ - 'import/no-cycle': ['error', { ignoreExternal: false, maxDepth: 3 }], - - /** - * https://github.com/import-js/eslint-plugin-import/blob/master/docs/rules/no-default-export.md - */ - 'import/no-default-export': 'error', - - /** - * https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-unresolved.md - */ - 'import/no-unresolved': ['error', { ignore: ['^virtual:'] }], - - /** - * https://github.com/import-js/eslint-plugin-import/blob/master/docs/rules/order.md - */ - 'import/order': [ - 'error', - { - alphabetize: { - order: 'asc', - caseInsensitive: true, - }, - groups: [['builtin', 'external'], 'internal', ['parent', 'index', 'sibling'], 'object'], - 'newlines-between': 'always', - }, - ], - - // ---------------------------------- - // eslint-plugin-n8n-local-rules - // ---------------------------------- - - 'n8n-local-rules/no-uncaught-json-parse': 'error', - - 'n8n-local-rules/no-json-parse-json-stringify': 'error', - - 'n8n-local-rules/no-unneeded-backticks': 'error', - - 'n8n-local-rules/no-interpolation-in-regular-string': 'error', - - 'n8n-local-rules/no-unused-param-in-catch-clause': 'error', - - 'n8n-local-rules/no-useless-catch-throw': 'error', - - 'n8n-local-rules/no-plain-errors': 'error', - - // ****************************************************************** - // overrides to base ruleset - // ****************************************************************** - - // ---------------------------------- - // ESLint - // ---------------------------------- - - /** - * https://eslint.org/docs/rules/class-methods-use-this - */ - 'class-methods-use-this': 'off', - - /** - * https://eslint.org/docs/rules/eqeqeq - */ - eqeqeq: 'error', - - /** - * https://eslint.org/docs/rules/no-plusplus - */ - 'no-plusplus': 'off', - - /** - * https://eslint.org/docs/rules/object-shorthand - */ - 'object-shorthand': 'error', - - /** - * https://eslint.org/docs/rules/prefer-const - */ - 'prefer-const': 'error', - - /** - * https://eslint.org/docs/rules/prefer-spread - */ - 'prefer-spread': 'error', - - // These are tuned off since we use `noUnusedLocals` and `noUnusedParameters` now - 'no-unused-vars': 'off', - '@typescript-eslint/no-unused-vars': 'off', - - /** - * https://www.typescriptlang.org/docs/handbook/enums.html#const-enums - */ - 'no-restricted-syntax': [ - 'error', - { - selector: 'TSEnumDeclaration:not([const=true])', - message: - 'Do not declare raw enums as it leads to runtime overhead. Use const enum instead. See https://www.typescriptlang.org/docs/handbook/enums.html#const-enums', - }, - ], - - // ---------------------------------- - // import - // ---------------------------------- - - /** - * https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/prefer-default-export.md - */ - 'import/prefer-default-export': 'off', - - // ---------------------------------- - // no-unused-imports - // ---------------------------------- - - /** - * https://github.com/sweepline/eslint-plugin-unused-imports/blob/master/docs/rules/no-unused-imports.md - */ - 'unused-imports/no-unused-imports': process.env.NODE_ENV === 'development' ? 'warn' : 'error', - - /** https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-await.md */ - 'unicorn/no-unnecessary-await': 'error', - - /** https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-promise-resolve-reject.md */ - 'unicorn/no-useless-promise-resolve-reject': 'error', - - 'lodash/path-style': ['error', 'as-needed'], - 'lodash/import-scope': ['error', 'method'], - }, - - overrides: [ - { - files: ['test/**/*.ts', '**/__tests__/*.ts', '**/*.cy.ts'], - rules: { - 'n8n-local-rules/no-plain-errors': 'off', - 'n8n-local-rules/no-skipped-tests': - process.env.NODE_ENV === 'development' ? 'warn' : 'error', - - // TODO: Remove these - '@typescript-eslint/ban-ts-comment': 'off', - '@typescript-eslint/naming-convention': 'off', - 'import/no-duplicates': 'off', - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/no-loop-func': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-shadow': 'off', - '@typescript-eslint/no-throw-literal': 'off', - '@typescript-eslint/no-unsafe-argument': 'off', - '@typescript-eslint/no-unsafe-assignment': 'off', - '@typescript-eslint/no-unsafe-call': 'off', - '@typescript-eslint/no-unsafe-member-access': 'off', - '@typescript-eslint/no-unsafe-return': 'off', - '@typescript-eslint/no-unused-expressions': 'off', - '@typescript-eslint/no-use-before-define': 'off', - '@typescript-eslint/no-var-requires': 'off', - '@typescript-eslint/prefer-nullish-coalescing': 'off', - '@typescript-eslint/prefer-optional-chain': 'off', - '@typescript-eslint/restrict-plus-operands': 'off', - '@typescript-eslint/restrict-template-expressions': 'off', - '@typescript-eslint/unbound-method': 'off', - 'id-denylist': 'off', - 'import/no-default-export': 'off', - 'import/no-extraneous-dependencies': 'off', - 'n8n-local-rules/no-uncaught-json-parse': 'off', - 'prefer-const': 'off', - 'prefer-spread': 'off', - }, - }, - ], -}); diff --git a/packages/@n8n/eslint-config/frontend.js b/packages/@n8n/eslint-config/frontend.js deleted file mode 100644 index e0051f10a1..0000000000 --- a/packages/@n8n/eslint-config/frontend.js +++ /dev/null @@ -1,102 +0,0 @@ -const isCI = process.env.CI === 'true'; - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - plugins: ['vue'], - - extends: ['plugin:vue/vue3-recommended', '@vue/typescript', './base'], - - env: { - browser: true, - es6: true, - node: true, - }, - - ignorePatterns: ['**/*.js', '**/*.d.ts', 'vite.config.ts', '**/*.ts.snap'], - - overrides: [ - { - files: ['**/*.test.ts', '**/test/**/*.ts', '**/__tests__/**/*.ts', '**/*.stories.ts'], - rules: { - 'import/no-extraneous-dependencies': 'off', - }, - }, - { - files: ['**/*.vue'], - rules: { - 'vue/no-deprecated-slot-attribute': 'error', - 'vue/no-deprecated-slot-scope-attribute': 'error', - 'vue/no-multiple-template-root': 'error', - 'vue/v-slot-style': 'error', - 'vue/no-unused-components': 'error', - 'vue/multi-word-component-names': 'off', - 'vue/component-name-in-template-casing': [ - 'error', - 'PascalCase', - { - registeredComponentsOnly: true, - }, - ], - 'vue/no-reserved-component-names': [ - 'error', - { - disallowVueBuiltInComponents: true, - disallowVue3BuiltInComponents: false, - }, - ], - 'vue/prop-name-casing': ['error', 'camelCase'], - 'vue/attribute-hyphenation': ['error', 'always'], - 'vue/define-emits-declaration': ['error', 'type-literal'], - 'vue/require-macro-variable-name': [ - 'error', - { - defineProps: 'props', - defineEmits: 'emit', - defineSlots: 'slots', - useSlots: 'slots', - useAttrs: 'attrs', - }, - ], - 'vue/block-order': [ - 'error', - { - order: ['script', 'template', 'style'], - }, - ], - 'vue/no-v-html': 'error', - - // TODO: remove these - 'vue/no-mutating-props': 'warn', - 'vue/no-side-effects-in-computed-properties': 'warn', - 'vue/no-v-text-v-html-on-component': 'warn', - 'vue/return-in-computed-property': 'warn', - }, - }, - ], - - rules: { - 'no-console': 'warn', - 'no-debugger': isCI ? 'error' : 'off', - semi: [2, 'always'], - 'comma-dangle': ['error', 'always-multiline'], - 'no-tabs': 0, - 'no-labels': 0, - '@typescript-eslint/no-use-before-define': 'off', - '@typescript-eslint/no-explicit-any': 'error', - 'import/no-extraneous-dependencies': 'warn', - - // TODO: fix these - '@typescript-eslint/no-unsafe-call': 'off', - '@typescript-eslint/no-unsafe-assignment': 'off', - '@typescript-eslint/no-unsafe-argument': 'off', - '@typescript-eslint/no-unsafe-return': 'off', - '@typescript-eslint/restrict-template-expressions': 'off', - '@typescript-eslint/unbound-method': 'off', - '@typescript-eslint/no-unsafe-member-access': 'off', - - // TODO: remove these - 'n8n-local-rules/no-plain-errors': 'off', - }, -}; diff --git a/packages/@n8n/eslint-config/jest.config.js b/packages/@n8n/eslint-config/jest.config.js deleted file mode 100644 index d6c48554a7..0000000000 --- a/packages/@n8n/eslint-config/jest.config.js +++ /dev/null @@ -1,2 +0,0 @@ -/** @type {import('jest').Config} */ -module.exports = require('../../../jest.config'); diff --git a/packages/@n8n/eslint-config/local-rules.js b/packages/@n8n/eslint-config/local-rules.js deleted file mode 100644 index e163cece27..0000000000 --- a/packages/@n8n/eslint-config/local-rules.js +++ /dev/null @@ -1,465 +0,0 @@ -'use strict'; - -const path = require('path'); - -/** - * This file contains any locally defined ESLint rules. They are picked up by - * eslint-plugin-n8n-local-rules and exposed as 'n8n-local-rules/'. - */ -module.exports = { - /** - * A rule to detect calls to JSON.parse() that are not wrapped inside try/catch blocks. - * - * Valid: - * ```js - * try { JSON.parse(foo) } catch(err) { baz() } - * ``` - * - * Invalid: - * ```js - * JSON.parse(foo) - * ``` - * - * The pattern where an object is cloned with JSON.parse(JSON.stringify()) is allowed - * (abundant in the n8n codebase): - * - * Valid: - * ```js - * JSON.parse(JSON.stringify(foo)) - * ``` - */ - 'no-uncaught-json-parse': { - meta: { - type: 'problem', - docs: { - description: - 'Calls to `JSON.parse()` must be replaced with `jsonParse()` from `n8n-workflow` or surrounded with a try/catch block.', - recommended: 'error', - }, - schema: [], - messages: { - noUncaughtJsonParse: - 'Use `jsonParse()` from `n8n-workflow` or surround the `JSON.parse()` call with a try/catch block.', - }, - }, - defaultOptions: [], - create(context) { - return { - CallExpression(node) { - if (!isJsonParseCall(node)) { - return; - } - - if (isJsonStringifyCall(node)) { - return; - } - - if (context.getAncestors().find((node) => node.type === 'TryStatement') !== undefined) { - return; - } - - // Found a JSON.parse() call not wrapped into a try/catch, so report it - context.report({ - messageId: 'noUncaughtJsonParse', - node, - }); - }, - }; - }, - }, - - 'no-json-parse-json-stringify': { - meta: { - type: 'problem', - docs: { - description: - 'Calls to `JSON.parse(JSON.stringify(arg))` must be replaced with `deepCopy(arg)` from `n8n-workflow`.', - recommended: 'error', - }, - messages: { - noJsonParseJsonStringify: 'Replace with `deepCopy({{ argText }})`', - }, - fixable: 'code', - }, - create(context) { - return { - CallExpression(node) { - if (isJsonParseCall(node) && isJsonStringifyCall(node)) { - const [callExpression] = node.arguments; - - const { arguments: args } = callExpression; - - if (!Array.isArray(args) || args.length !== 1) return; - - const [arg] = args; - - if (!arg) return; - - const argText = context.getSourceCode().getText(arg); - - context.report({ - messageId: 'noJsonParseJsonStringify', - node, - data: { argText }, - fix: (fixer) => fixer.replaceText(node, `deepCopy(${argText})`), - }); - } - }, - }; - }, - }, - - 'no-unneeded-backticks': { - meta: { - type: 'problem', - docs: { - description: - 'Template literal backticks may only be used for string interpolation or multiline strings.', - recommended: 'error', - }, - messages: { - noUneededBackticks: 'Use single or double quotes, not backticks', - }, - fixable: 'code', - }, - create(context) { - return { - TemplateLiteral(node) { - if (node.expressions.length > 0) return; - if (node.quasis.every((q) => q.loc.start.line !== q.loc.end.line)) return; - - node.quasis.forEach((q) => { - const escaped = q.value.raw.replace(/(? fixer.replaceText(q, `'${escaped}'`), - }); - }); - }, - }; - }, - }, - - 'no-unused-param-in-catch-clause': { - meta: { - type: 'problem', - docs: { - description: 'Unused param in catch clause must be omitted.', - recommended: 'error', - }, - messages: { - removeUnusedParam: 'Remove unused param in catch clause', - }, - fixable: 'code', - }, - create(context) { - return { - CatchClause(node) { - if (node.param?.name?.startsWith('_')) { - const start = node.range[0] + 'catch '.length; - const end = node.param.range[1] + '()'.length; - - context.report({ - messageId: 'removeUnusedParam', - node, - fix: (fixer) => fixer.removeRange([start, end]), - }); - } - }, - }; - }, - }, - - 'no-useless-catch-throw': { - meta: { - type: 'problem', - docs: { - description: 'Disallow `try-catch` blocks where the `catch` only contains a `throw error`.', - recommended: 'error', - }, - messages: { - noUselessCatchThrow: 'Remove useless `catch` block.', - }, - fixable: 'code', - }, - create(context) { - return { - CatchClause(node) { - if ( - node.body.body.length === 1 && - node.body.body[0].type === 'ThrowStatement' && - node.body.body[0].argument.type === 'Identifier' && - node.body.body[0].argument.name === node.param.name - ) { - context.report({ - node, - messageId: 'noUselessCatchThrow', - fix(fixer) { - const tryStatement = node.parent; - const tryBlock = tryStatement.block; - const sourceCode = context.getSourceCode(); - const tryBlockText = sourceCode.getText(tryBlock); - const tryBlockTextWithoutBraces = tryBlockText.slice(1, -1).trim(); - const indentedTryBlockText = tryBlockTextWithoutBraces - .split('\n') - .map((line) => line.replace(/\t/, '')) - .join('\n'); - return fixer.replaceText(tryStatement, indentedTryBlockText); - }, - }); - } - }, - }; - }, - }, - - 'no-skipped-tests': { - meta: { - type: 'problem', - docs: { - description: 'Tests must not be skipped.', - recommended: 'error', - }, - messages: { - removeSkip: 'Remove `.skip()` call', - removeOnly: 'Remove `.only()` call', - removeXPrefix: 'Remove `x` prefix', - }, - fixable: 'code', - }, - create(context) { - const TESTING_FUNCTIONS = new Set(['test', 'it', 'describe']); - const SKIPPING_METHODS = new Set(['skip', 'only']); - const PREFIXED_TESTING_FUNCTIONS = new Set(['xtest', 'xit', 'xdescribe']); - const toMessageId = (s) => 'remove' + s.charAt(0).toUpperCase() + s.slice(1); - - return { - MemberExpression(node) { - if ( - node.object.type === 'Identifier' && - TESTING_FUNCTIONS.has(node.object.name) && - node.property.type === 'Identifier' && - SKIPPING_METHODS.has(node.property.name) - ) { - context.report({ - messageId: toMessageId(node.property.name), - node, - fix: (fixer) => { - const [start, end] = node.property.range; - return fixer.removeRange([start - '.'.length, end]); - }, - }); - } - }, - CallExpression(node) { - if ( - node.callee.type === 'Identifier' && - PREFIXED_TESTING_FUNCTIONS.has(node.callee.name) - ) { - context.report({ - messageId: 'removeXPrefix', - node, - fix: (fixer) => fixer.replaceText(node.callee, 'test'), - }); - } - }, - }; - }, - }, - - 'no-interpolation-in-regular-string': { - meta: { - type: 'problem', - docs: { - description: - 'String interpolation `${...}` requires backticks, not single or double quotes.', - recommended: 'error', - }, - messages: { - useBackticks: 'Use backticks to interpolate', - }, - fixable: 'code', - }, - create(context) { - return { - Literal(node) { - if (typeof node.value !== 'string') return; - - if (/\$\{/.test(node.value)) { - context.report({ - messageId: 'useBackticks', - node, - fix: (fixer) => fixer.replaceText(node, `\`${node.value}\``), - }); - } - }, - }; - }, - }, - - 'no-plain-errors': { - meta: { - type: 'problem', - docs: { - description: - 'Only `ApplicationError` (from the `workflow` package) or its child classes must be thrown. This ensures the error will be normalized when reported to Sentry, if applicable.', - recommended: 'error', - }, - messages: { - useApplicationError: - 'Throw an `ApplicationError` (from the `workflow` package) or its child classes.', - }, - fixable: 'code', - }, - create(context) { - return { - ThrowStatement(node) { - if (!node.argument) return; - - const isNewError = - node.argument.type === 'NewExpression' && node.argument.callee.name === 'Error'; - - const isNewlessError = - node.argument.type === 'CallExpression' && node.argument.callee.name === 'Error'; - - if (isNewError || isNewlessError) { - return context.report({ - messageId: 'useApplicationError', - node, - fix: (fixer) => - fixer.replaceText( - node, - `throw new ApplicationError(${node.argument.arguments - .map((arg) => arg.raw) - .join(', ')})`, - ), - }); - } - }, - }; - }, - }, - - 'no-dynamic-import-template': { - meta: { - type: 'error', - docs: { - description: - 'Disallow non-relative imports in template string argument to `await import()`, because `tsc-alias` as of 1.8.7 is unable to resolve aliased paths in this scenario.', - recommended: true, - }, - }, - create: function (context) { - return { - 'AwaitExpression > ImportExpression TemplateLiteral'(node) { - const templateValue = node.quasis[0].value.cooked; - - if (!templateValue?.startsWith('@/')) return; - - context.report({ - node, - message: - 'Use relative imports in template string argument to `await import()`, because `tsc-alias` as of 1.8.7 is unable to resolve aliased paths in this scenario.', - }); - }, - }; - }, - }, - - 'misplaced-n8n-typeorm-import': { - meta: { - type: 'error', - docs: { - description: 'Ensure `@n8n/typeorm` is imported only from within the `@n8n/db` package.', - recommended: 'error', - }, - messages: { - moveImport: 'Please move this import to `@n8n/db`.', - }, - }, - create(context) { - return { - ImportDeclaration(node) { - if (node.source.value === '@n8n/typeorm' && !context.getFilename().includes('@n8n/db')) { - context.report({ node, messageId: 'moveImport' }); - } - }, - }; - }, - }, - - 'no-type-unsafe-event-emitter': { - meta: { - type: 'problem', - docs: { - description: 'Disallow extending from `EventEmitter`, which is not type-safe.', - recommended: 'error', - }, - messages: { - noExtendsEventEmitter: 'Extend from the type-safe `TypedEmitter` class instead.', - }, - }, - create(context) { - return { - ClassDeclaration(node) { - if ( - node.superClass && - node.superClass.type === 'Identifier' && - node.superClass.name === 'EventEmitter' && - node.id.name !== 'TypedEmitter' - ) { - context.report({ - node: node.superClass, - messageId: 'noExtendsEventEmitter', - }); - } - }, - }; - }, - }, - - 'no-untyped-config-class-field': { - meta: { - type: 'problem', - docs: { - description: 'Enforce explicit typing of config class fields', - recommended: 'error', - }, - messages: { - noUntypedConfigClassField: - 'Class field must have an explicit type annotation, e.g. `field: type = value`. See: https://github.com/n8n-io/n8n/pull/10433', - }, - }, - create(context) { - return { - PropertyDefinition(node) { - if (!node.typeAnnotation) { - context.report({ node: node.key, messageId: 'noUntypedConfigClassField' }); - } - }, - }; - }, - }, -}; - -const isJsonParseCall = (node) => - node.callee.type === 'MemberExpression' && - node.callee.object.type === 'Identifier' && - node.callee.object.name === 'JSON' && - node.callee.property.type === 'Identifier' && - node.callee.property.name === 'parse'; - -const isJsonStringifyCall = (node) => { - const parseArg = node.arguments?.[0]; - return ( - parseArg !== undefined && - parseArg.type === 'CallExpression' && - parseArg.callee.type === 'MemberExpression' && - parseArg.callee.object.type === 'Identifier' && - parseArg.callee.object.name === 'JSON' && - parseArg.callee.property.type === 'Identifier' && - parseArg.callee.property.name === 'stringify' - ); -}; diff --git a/packages/@n8n/eslint-config/local-rules.test.js b/packages/@n8n/eslint-config/local-rules.test.js deleted file mode 100644 index ca32c37a40..0000000000 --- a/packages/@n8n/eslint-config/local-rules.test.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; - -const rules = require('./local-rules'), - RuleTester = require('eslint').RuleTester; - -const ruleTester = new RuleTester(); - -ruleTester.run('no-uncaught-json-parse', rules['no-uncaught-json-parse'], { - valid: [ - { - code: 'try { JSON.parse(foo) } catch (e) {}', - }, - { - code: 'JSON.parse(JSON.stringify(foo))', - }, - ], - invalid: [ - { - code: 'JSON.parse(foo)', - errors: [{ messageId: 'noUncaughtJsonParse' }], - }, - ], -}); - -ruleTester.run('no-json-parse-json-stringify', rules['no-json-parse-json-stringify'], { - valid: [ - { - code: 'deepCopy(foo)', - }, - ], - invalid: [ - { - code: 'JSON.parse(JSON.stringify(foo))', - errors: [{ messageId: 'noJsonParseJsonStringify' }], - output: 'deepCopy(foo)', - }, - { - code: 'JSON.parse(JSON.stringify(foo.bar))', - errors: [{ messageId: 'noJsonParseJsonStringify' }], - output: 'deepCopy(foo.bar)', - }, - { - code: 'JSON.parse(JSON.stringify(foo.bar.baz))', - errors: [{ messageId: 'noJsonParseJsonStringify' }], - output: 'deepCopy(foo.bar.baz)', - }, - { - code: 'JSON.parse(JSON.stringify(foo.bar[baz]))', - errors: [{ messageId: 'noJsonParseJsonStringify' }], - output: 'deepCopy(foo.bar[baz])', - }, - ], -}); - -ruleTester.run('no-useless-catch-throw', rules['no-useless-catch-throw'], { - valid: [ - { - code: 'try { foo(); } catch (e) { console.error(e); }', - }, - { - code: 'try { foo(); } catch (e) { throw new Error("Custom error"); }', - }, - ], - invalid: [ - { - code: ` -try { - // Some comment - if (foo) { - bar(); - } -} catch (e) { - throw e; -}`, - errors: [{ messageId: 'noUselessCatchThrow' }], - output: ` -// Some comment -if (foo) { - bar(); -}`, - }, - ], -}); diff --git a/packages/@n8n/eslint-config/node.js b/packages/@n8n/eslint-config/node.js deleted file mode 100644 index ff4ce11ed1..0000000000 --- a/packages/@n8n/eslint-config/node.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['./base'], - - env: { - es6: true, - node: true, - }, -}; diff --git a/packages/@n8n/eslint-config/package.json b/packages/@n8n/eslint-config/package.json index 84b2e24633..6bbd089d51 100644 --- a/packages/@n8n/eslint-config/package.json +++ b/packages/@n8n/eslint-config/package.json @@ -1,33 +1,57 @@ { - "name": "@n8n/eslint-config", "private": true, + "name": "@n8n/eslint-config", + "type": "module", "version": "0.0.1", "exports": { - "./base": "./base.js", - "./frontend": "./frontend.js", - "./local-rules": "./local-rules.js", - "./node": "./node.js", - "./shared": "./shared.js" - }, - "devDependencies": { - "@types/eslint": "^8.56.5", - "@typescript-eslint/eslint-plugin": "^7.2.0", - "@typescript-eslint/parser": "^7.2.0", - "@vue/eslint-config-typescript": "^13.0.0", - "eslint": "^8.57.0", - "eslint-config-airbnb-typescript": "^18.0.0", - "eslint-config-prettier": "^9.1.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-lodash": "^7.4.0", - "eslint-plugin-n8n-local-rules": "^1.0.0", - "eslint-plugin-unicorn": "^51.0.1", - "eslint-plugin-unused-imports": "^3.1.0", - "eslint-plugin-vue": "^9.23.0", - "vue-eslint-parser": "^9.4.2" + "./base": { + "default": "./dist/configs/base.js", + "types": "./dist/configs/base.d.js" + }, + "./frontend": { + "default": "./dist/configs/frontend.js", + "types": "./dist/configs/frontend.d.js" + }, + "./node": { + "default": "./dist/configs/node.js", + "types": "./dist/configs/node.d.js" + } }, "scripts": { - "clean": "rimraf .turbo", - "test": "jest" + "build": "tsc", + "clean": "rimraf dist .turbo", + "dev": "pnpm watch", + "format": "biome format --write .", + "format:check": "biome ci .", + "test": "vitest run", + "test:dev": "vitest", + "typecheck": "tsc --noEmit", + "watch": "tsc --watch" + }, + "devDependencies": { + "@eslint/js": "^9.29.0", + "@n8n/typescript-config": "workspace:*", + "@n8n/vitest-config": "workspace:*", + "@stylistic/eslint-plugin": "^5.0.0", + "@types/eslint": "^9.6.1", + "@typescript-eslint/eslint-plugin": "^8.35.0", + "@typescript-eslint/rule-tester": "^8.35.0", + "@typescript-eslint/utils": "^8.35.0", + "eslint-config-prettier": "^10.1.5", + "eslint-import-resolver-typescript": "^4.4.3", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-import-x": "^4.15.2", + "eslint-plugin-lodash": "^8.0.0", + "eslint-plugin-unicorn": "^59.0.1", + "eslint-plugin-unused-imports": "^4.1.4", + "eslint-plugin-vue": "^10.2.0", + "globals": "^16.2.0", + "tsup": "catalog:", + "typescript": "catalog:", + "typescript-eslint": "^8.35.0", + "vitest": "catalog:" + }, + "peerDependencies": { + "eslint": ">= 9" } } diff --git a/packages/@n8n/eslint-config/shared.js b/packages/@n8n/eslint-config/shared.js deleted file mode 100644 index f77a70df2f..0000000000 --- a/packages/@n8n/eslint-config/shared.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @type {(dir: string, mode: 'frontend' | undefined) => import('@types/eslint').ESLint.ConfigData} - */ -module.exports = (tsconfigRootDir, mode) => { - const isFrontend = mode === 'frontend'; - const parser = isFrontend ? 'vue-eslint-parser' : '@typescript-eslint/parser'; - const extraParserOptions = isFrontend - ? { - extraFileExtensions: ['.vue'], - parser: { - ts: '@typescript-eslint/parser', - js: '@typescript-eslint/parser', - vue: 'vue-eslint-parser', - template: 'vue-eslint-parser', - }, - } - : {}; - - const settings = { - 'import/parsers': { - '@typescript-eslint/parser': ['.ts'], - }, - - 'import/resolver': { - typescript: { - tsconfigRootDir, - project: './tsconfig.json', - }, - }, - }; - - return { - parser, - parserOptions: { - tsconfigRootDir, - project: ['./tsconfig.json'], - ...extraParserOptions, - }, - settings, - }; -}; diff --git a/packages/@n8n/eslint-config/src/configs/base.ts b/packages/@n8n/eslint-config/src/configs/base.ts new file mode 100644 index 0000000000..8a02a14e7f --- /dev/null +++ b/packages/@n8n/eslint-config/src/configs/base.ts @@ -0,0 +1,423 @@ +import { globalIgnores } from 'eslint/config'; +import eslint from '@eslint/js'; +import importPlugin from 'eslint-plugin-import-x'; +import typescriptPlugin from '@typescript-eslint/eslint-plugin'; +import unusedImportsPlugin from 'eslint-plugin-unused-imports'; +import stylisticPlugin from '@stylistic/eslint-plugin'; +import unicornPlugin from 'eslint-plugin-unicorn'; +import lodashPlugin from 'eslint-plugin-lodash'; +import { localRulesPlugin } from '../plugin.js'; +import tseslint from 'typescript-eslint'; +import eslintConfigPrettier from 'eslint-config-prettier/flat'; +import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript'; + +// Slowest rules are disabled locally to improve performance in development +// They are enabled in CI to ensure code quality +const runAllRules = process.env.CI === 'true' || process.env.INCLUDE_SLOW_RULES === 'true'; + +export const baseConfig = tseslint.config( + globalIgnores([ + 'node_modules/**', + 'dist/**', + 'eslint.config.mjs', + 'tsup.config.ts', + 'jest.config.js', + 'cypress.config.js', + ]), + eslint.configs.recommended, + tseslint.configs.recommended, + tseslint.configs.recommendedTypeChecked, + importPlugin.flatConfigs.recommended, + importPlugin.flatConfigs.typescript, + eslintConfigPrettier, + localRulesPlugin.configs.recommended, + { + plugins: { + 'unused-imports': unusedImportsPlugin, + '@stylistic': stylisticPlugin, + lodash: lodashPlugin, + unicorn: unicornPlugin, + '@typescript-eslint': typescriptPlugin, + }, + languageOptions: { + parserOptions: { + projectService: true, + }, + }, + settings: { + 'import-x/resolver-next': [createTypeScriptImportResolver()], + }, + rules: { + // ****************************************************************** + // additions to base ruleset + // ****************************************************************** + + // ---------------------------------- + // ESLint + // ---------------------------------- + + /** + * https://eslint.org/docs/rules/id-denylist + */ + 'id-denylist': [ + 'error', + 'err', + 'cb', + 'callback', + 'any', + 'Number', + 'number', + 'String', + 'string', + 'Boolean', + 'boolean', + 'Undefined', + 'undefined', + ], + + /** + * https://eslint.org/docs/latest/rules/no-void + */ + 'no-void': ['error', { allowAsStatement: true }], + + /** + * https://eslint.org/docs/latest/rules/indent + * + * Delegated to Prettier. + */ + indent: 'off', + + /** + * https://eslint.org/docs/latest/rules/no-constant-binary-expression + */ + 'no-constant-binary-expression': 'error', + + /** + * https://eslint.org/docs/latest/rules/sort-imports + */ + 'sort-imports': 'off', // @TECH_DEBT: Enable, prefs to be decided - N8N-5821 + + // ---------------------------------- + // @typescript-eslint + // ---------------------------------- + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/array-type.md + */ + '@typescript-eslint/array-type': ['error', { default: 'array-simple' }], + + /** https://typescript-eslint.io/rules/await-thenable/ */ + '@typescript-eslint/await-thenable': runAllRules ? 'error' : 'off', + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-ts-comment.md + */ + '@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': true }], + + /** + * https://typescript-eslint.io/rules/no-restricted-types + */ + '@typescript-eslint/no-restricted-types': [ + 'error', + { + types: { + Object: { + message: 'Use object instead', + fixWith: 'object', + }, + String: { + message: 'Use string instead', + fixWith: 'string', + }, + Boolean: { + message: 'Use boolean instead', + fixWith: 'boolean', + }, + Number: { + message: 'Use number instead', + fixWith: 'number', + }, + Symbol: { + message: 'Use symbol instead', + fixWith: 'symbol', + }, + Function: { + message: [ + 'The `Function` type accepts any function-like value.', + 'It provides no type safety when calling the function, which can be a common source of bugs.', + 'It also accepts things like class declarations, which will throw at runtime as they will not be called with `new`.', + 'If you are expecting the function to accept certain arguments, you should explicitly define the function shape.', + ].join('\n'), + }, + }, + }, + ], + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-assertions.md + */ + '@typescript-eslint/consistent-type-assertions': 'error', + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-imports.md + */ + '@typescript-eslint/consistent-type-imports': 'error', + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-delimiter-style.md + */ + '@stylistic/member-delimiter-style': [ + 'error', + { + multiline: { + delimiter: 'semi', + requireLast: true, + }, + singleline: { + delimiter: 'semi', + requireLast: false, + }, + }, + ], + + // Not needed because we use Biome formatting + '@stylistic/ident': 'off', + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/naming-convention.md + */ + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: 'default', + format: ['camelCase'], + }, + { + selector: 'import', + format: ['camelCase', 'PascalCase'], + }, + { + selector: 'variable', + format: ['camelCase', 'snake_case', 'UPPER_CASE', 'PascalCase'], + leadingUnderscore: 'allowSingleOrDouble', + trailingUnderscore: 'allowSingleOrDouble', + }, + { + selector: 'property', + format: ['camelCase', 'snake_case', 'UPPER_CASE'], + leadingUnderscore: 'allowSingleOrDouble', + trailingUnderscore: 'allowSingleOrDouble', + }, + { + selector: 'typeLike', + format: ['PascalCase'], + }, + { + selector: ['method', 'function', 'parameter'], + format: ['camelCase'], + leadingUnderscore: 'allowSingleOrDouble', + }, + ], + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-invalid-void-type.md + */ + '@typescript-eslint/no-invalid-void-type': 'error', + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-misused-promises.md + */ + '@typescript-eslint/no-misused-promises': runAllRules + ? ['error', { checksVoidReturn: false }] + : 'off', + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/v4.30.0/packages/eslint-plugin/docs/rules/no-floating-promises.md + */ + '@typescript-eslint/no-floating-promises': runAllRules + ? ['error', { ignoreVoid: true }] + : 'off', + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/v4.33.0/packages/eslint-plugin/docs/rules/no-namespace.md + */ + '@typescript-eslint/no-namespace': 'off', + + /** + * https://typescript-eslint.io/rules/only-throw-error/ + */ + '@typescript-eslint/only-throw-error': 'error', + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-boolean-literal-compare.md + */ + '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error', + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md + */ + '@typescript-eslint/no-unnecessary-qualifier': runAllRules ? 'error' : 'off', + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-expressions.md + */ + '@typescript-eslint/no-unused-expressions': 'error', + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md + */ + '@typescript-eslint/prefer-nullish-coalescing': 'error', + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-optional-chain.md + */ + '@typescript-eslint/prefer-optional-chain': 'error', + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/promise-function-async.md + */ + '@typescript-eslint/promise-function-async': runAllRules ? 'error' : 'off', + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/triple-slash-reference.md + */ + '@typescript-eslint/triple-slash-reference': 'off', // @TECH_DEBT: Enable, disallowing in all cases - N8N-5820 + + /** + * https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/return-await.md + */ + '@typescript-eslint/return-await': ['error', 'always'], + + /** + * https://typescript-eslint.io/rules/explicit-member-accessibility/ + */ + '@typescript-eslint/explicit-member-accessibility': ['error', { accessibility: 'no-public' }], + + // ---------------------------------- + // eslint-plugin-import + // ---------------------------------- + + /** + * https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-cycle.md + */ + 'import-x/no-cycle': runAllRules ? ['error', { ignoreExternal: false, maxDepth: 3 }] : 'off', + + /** + * https://github.com/import-js/eslint-plugin-import/blob/master/docs/rules/no-default-export.md + */ + 'import-x/no-default-export': 'error', + + /** + * https://github.com/import-js/eslint-plugin-import/blob/master/docs/rules/order.md + */ + 'import-x/order': [ + 'error', + { + alphabetize: { + order: 'asc', + caseInsensitive: true, + }, + groups: [['builtin', 'external'], 'internal', ['parent', 'index', 'sibling'], 'object'], + 'newlines-between': 'always', + }, + ], + + /** + * https://github.com/import-js/eslint-plugin-import/blob/HEAD/docs/rules/no-duplicates.md + */ + 'import-x/no-duplicates': 'error', + + /** + * https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/prefer-default-export.md + */ + 'import-x/prefer-default-export': 'off', + + // These rules are not needed as TypeScript handles them + 'import-x/named': 'off', + 'import-x/namespace': 'off', + 'import-x/default': 'off', + 'import-x/no-named-as-default-member': 'off', + 'import-x/no-unresolved': 'off', + + // ****************************************************************** + // overrides to base ruleset + // ****************************************************************** + + // ---------------------------------- + // ESLint + // ---------------------------------- + + /** + * https://eslint.org/docs/rules/class-methods-use-this + */ + 'class-methods-use-this': 'off', + + /** + * https://eslint.org/docs/rules/eqeqeq + */ + eqeqeq: 'error', + + /** + * https://eslint.org/docs/rules/no-plusplus + */ + 'no-plusplus': 'off', + + /** + * https://eslint.org/docs/rules/object-shorthand + */ + 'object-shorthand': 'error', + + /** + * https://eslint.org/docs/rules/prefer-const + */ + 'prefer-const': 'error', + + /** + * https://eslint.org/docs/rules/prefer-spread + */ + 'prefer-spread': 'error', + + // These are tuned off since we use `noUnusedLocals` and `noUnusedParameters` now + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': 'off', + + /** + * https://www.typescriptlang.org/docs/handbook/enums.html#const-enums + */ + 'no-restricted-syntax': [ + 'error', + { + selector: 'TSEnumDeclaration:not([const=true])', + message: + 'Do not declare raw enums as it leads to runtime overhead. Use const enum instead. See https://www.typescriptlang.org/docs/handbook/enums.html#const-enums', + }, + ], + + // ---------------------------------- + // no-unused-imports + // ---------------------------------- + + /** + * https://github.com/sweepline/eslint-plugin-unused-imports/blob/master/docs/rules/no-unused-imports.md + */ + 'unused-imports/no-unused-imports': process.env.NODE_ENV === 'development' ? 'warn' : 'error', + + /** https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unnecessary-await.md */ + 'unicorn/no-unnecessary-await': 'error', + + /** https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-promise-resolve-reject.md */ + 'unicorn/no-useless-promise-resolve-reject': 'error', + + 'lodash/path-style': ['error', 'as-needed'], + 'lodash/import-scope': ['error', 'method'], + }, + }, + { + // Rules for unit tests + files: ['test/**/*.ts', '**/__tests__/*.ts', '**/*.test.ts', '**/*.cy.ts'], + rules: { + 'n8n-local-rules/no-plain-errors': 'off', + 'n8n-local-rules/no-skipped-tests': process.env.NODE_ENV === 'development' ? 'warn' : 'error', + }, + }, +); diff --git a/packages/@n8n/eslint-config/src/configs/frontend.ts b/packages/@n8n/eslint-config/src/configs/frontend.ts new file mode 100644 index 0000000000..9c3c1cd0e1 --- /dev/null +++ b/packages/@n8n/eslint-config/src/configs/frontend.ts @@ -0,0 +1,101 @@ +import { globalIgnores } from 'eslint/config'; +import tseslint from 'typescript-eslint'; +import VuePlugin from 'eslint-plugin-vue'; +import globals from 'globals'; +import { baseConfig } from './base.js'; + +const isCI = process.env.CI === 'true'; +const extraFileExtensions = ['.vue']; +const allGlobals = { NodeJS: true, ...globals.node, ...globals.browser }; + +export const frontendConfig = tseslint.config( + globalIgnores(['**/*.js', '**/*.d.ts', 'vite.config.ts', '**/*.ts.snap']), + baseConfig, + VuePlugin.configs['flat/recommended'], + { + rules: { + 'no-console': 'warn', + 'no-debugger': isCI ? 'error' : 'off', + semi: [2, 'always'], + 'comma-dangle': ['error', 'always-multiline'], + '@typescript-eslint/no-use-before-define': 'warn', + '@typescript-eslint/no-explicit-any': 'error', + }, + }, + { + files: ['**/*.ts'], + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + globals: allGlobals, + parser: tseslint.parser, + parserOptions: { projectService: true, extraFileExtensions }, + }, + }, + { + files: ['**/*.test.ts', '**/test/**/*.ts', '**/__tests__/**/*.ts', '**/*.stories.ts'], + rules: { + 'import-x/no-extraneous-dependencies': 'warn', + }, + }, + { + files: ['**/*.vue'], + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + globals: allGlobals, + parserOptions: { + parser: tseslint.parser, + extraFileExtensions, + }, + }, + rules: { + 'vue/no-deprecated-slot-attribute': 'error', + 'vue/no-deprecated-slot-scope-attribute': 'error', + 'vue/no-multiple-template-root': 'error', + 'vue/v-slot-style': 'error', + 'vue/no-unused-components': 'error', + 'vue/multi-word-component-names': 'off', + 'vue/component-name-in-template-casing': [ + 'error', + 'PascalCase', + { + registeredComponentsOnly: true, + }, + ], + 'vue/no-reserved-component-names': [ + 'error', + { + disallowVueBuiltInComponents: true, + disallowVue3BuiltInComponents: false, + }, + ], + 'vue/prop-name-casing': ['error', 'camelCase'], + 'vue/attribute-hyphenation': ['error', 'always'], + 'vue/define-emits-declaration': ['error', 'type-literal'], + 'vue/require-macro-variable-name': [ + 'error', + { + defineProps: 'props', + defineEmits: 'emit', + defineSlots: 'slots', + useSlots: 'slots', + useAttrs: 'attrs', + }, + ], + 'vue/block-order': [ + 'error', + { + order: ['script', 'template', 'style'], + }, + ], + 'vue/no-v-html': 'error', + + // TODO: remove these + 'vue/no-mutating-props': 'warn', + 'vue/no-side-effects-in-computed-properties': 'warn', + 'vue/no-v-text-v-html-on-component': 'warn', + 'vue/return-in-computed-property': 'warn', + }, + }, +); diff --git a/packages/@n8n/eslint-config/src/configs/node.ts b/packages/@n8n/eslint-config/src/configs/node.ts new file mode 100644 index 0000000000..ddf126d28f --- /dev/null +++ b/packages/@n8n/eslint-config/src/configs/node.ts @@ -0,0 +1,10 @@ +import tseslint from 'typescript-eslint'; +import globals from 'globals'; +import { baseConfig } from './base.js'; + +export const nodeConfig = tseslint.config(baseConfig, { + languageOptions: { + ecmaVersion: 2024, + globals: globals.node, + }, +}); diff --git a/packages/@n8n/eslint-config/src/plugin.ts b/packages/@n8n/eslint-config/src/plugin.ts new file mode 100644 index 0000000000..da81092122 --- /dev/null +++ b/packages/@n8n/eslint-config/src/plugin.ts @@ -0,0 +1,30 @@ +import type { ESLint } from 'eslint'; +import { rules } from './rules/index.js'; + +const plugin = { + meta: { + name: 'n8n-local-rules', + }, + configs: {}, + // @ts-expect-error Rules type does not match for typescript-eslint and eslint + rules: rules as ESLint.Plugin['rules'], +} satisfies ESLint.Plugin; + +export const localRulesPlugin = { + ...plugin, + configs: { + recommended: { + plugins: { + 'n8n-local-rules': plugin, + }, + rules: { + 'n8n-local-rules/no-uncaught-json-parse': 'error', + 'n8n-local-rules/no-json-parse-json-stringify': 'error', + 'n8n-local-rules/no-unneeded-backticks': 'error', + 'n8n-local-rules/no-interpolation-in-regular-string': 'error', + 'n8n-local-rules/no-unused-param-in-catch-clause': 'error', + 'n8n-local-rules/no-useless-catch-throw': 'error', + }, + }, + }, +} satisfies ESLint.Plugin; diff --git a/packages/@n8n/eslint-config/src/plugins.d.ts b/packages/@n8n/eslint-config/src/plugins.d.ts new file mode 100644 index 0000000000..8335ec1074 --- /dev/null +++ b/packages/@n8n/eslint-config/src/plugins.d.ts @@ -0,0 +1 @@ +declare module 'eslint-plugin-lodash'; diff --git a/packages/@n8n/eslint-config/src/rules/index.ts b/packages/@n8n/eslint-config/src/rules/index.ts new file mode 100644 index 0000000000..df6e25c751 --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/index.ts @@ -0,0 +1,28 @@ +import { NoJsonParseJsonStringifyRule } from './no-json-parse-json-stringify.js'; +import { NoUncaughtJsonParseRule } from './no-uncaught-json-parse.js'; +import { NoUnneededBackticksRule } from './no-unneeded-backticks.js'; +import { NoUnusedParamInCatchClauseRule } from './no-unused-param-catch-clause.js'; +import { NoUselessCatchThrowRule } from './no-useless-catch-throw.js'; +import { NoSkippedTestsRule } from './no-skipped-tests.js'; +import { NoInterpolationInRegularStringRule } from './no-interpolation-in-regular-string.js'; +import { NoPlainErrorsRule } from './no-plain-errors.js'; +import { NoDynamicImportTemplateRule } from './no-dynamic-import-template.js'; +import { MisplacedN8nTypeormImportRule } from './misplaced-n8n-typeorm-import.js'; +import { NoTypeUnsafeEventEmitterRule } from './no-type-unsafe-event-emitter.js'; +import { NoUntypedConfigClassFieldRule } from './no-untyped-config-class-field.js'; +import type { AnyRuleModule } from '@typescript-eslint/utils/ts-eslint'; + +export const rules = { + 'no-uncaught-json-parse': NoUncaughtJsonParseRule, + 'no-json-parse-json-stringify': NoJsonParseJsonStringifyRule, + 'no-unneeded-backticks': NoUnneededBackticksRule, + 'no-unused-param-in-catch-clause': NoUnusedParamInCatchClauseRule, + 'no-useless-catch-throw': NoUselessCatchThrowRule, + 'no-skipped-tests': NoSkippedTestsRule, + 'no-interpolation-in-regular-string': NoInterpolationInRegularStringRule, + 'no-plain-errors': NoPlainErrorsRule, + 'no-dynamic-import-template': NoDynamicImportTemplateRule, + 'misplaced-n8n-typeorm-import': MisplacedN8nTypeormImportRule, + 'no-type-unsafe-event-emitter': NoTypeUnsafeEventEmitterRule, + 'no-untyped-config-class-field': NoUntypedConfigClassFieldRule, +} satisfies Record; diff --git a/packages/@n8n/eslint-config/src/rules/misplaced-n8n-typeorm-import.ts b/packages/@n8n/eslint-config/src/rules/misplaced-n8n-typeorm-import.ts new file mode 100644 index 0000000000..8a64db6b79 --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/misplaced-n8n-typeorm-import.ts @@ -0,0 +1,24 @@ +import { ESLintUtils } from '@typescript-eslint/utils'; + +export const MisplacedN8nTypeormImportRule = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + type: 'problem', + docs: { + description: 'Ensure `@n8n/typeorm` is imported only from within the `@n8n/db` package.', + }, + messages: { + moveImport: 'Please move this import to `@n8n/db`.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + return { + ImportDeclaration(node) { + if (node.source.value === '@n8n/typeorm' && !context.filename.includes('@n8n/db')) { + context.report({ node, messageId: 'moveImport' }); + } + }, + }; + }, +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-dynamic-import-template.ts b/packages/@n8n/eslint-config/src/rules/no-dynamic-import-template.ts new file mode 100644 index 0000000000..57f3168caf --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-dynamic-import-template.ts @@ -0,0 +1,31 @@ +import { ESLintUtils, type TSESTree } from '@typescript-eslint/utils'; + +export const NoDynamicImportTemplateRule = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + type: 'problem', + docs: { + description: + 'Disallow non-relative imports in template string argument to `await import()`, because `tsc-alias` as of 1.8.7 is unable to resolve aliased paths in this scenario.', + }, + schema: [], + messages: { + noDynamicImportTemplate: + 'Use relative imports in template string argument to `await import()`, because `tsc-alias` as of 1.8.7 is unable to resolve aliased paths in this scenario.', + }, + }, + defaultOptions: [], + create(context) { + return { + 'AwaitExpression > ImportExpression TemplateLiteral'(node: TSESTree.TemplateLiteral) { + const templateValue = node.quasis[0].value.cooked; + + if (!templateValue?.startsWith('@/')) return; + + context.report({ + node, + messageId: 'noDynamicImportTemplate', + }); + }, + }; + }, +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-interpolation-in-regular-string.ts b/packages/@n8n/eslint-config/src/rules/no-interpolation-in-regular-string.ts new file mode 100644 index 0000000000..195f4ad348 --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-interpolation-in-regular-string.ts @@ -0,0 +1,31 @@ +import { ESLintUtils } from '@typescript-eslint/utils'; + +export const NoInterpolationInRegularStringRule = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + type: 'problem', + docs: { + description: 'String interpolation `${...}` requires backticks, not single or double quotes.', + }, + messages: { + useBackticks: 'Use backticks to interpolate', + }, + fixable: 'code', + schema: [], + }, + defaultOptions: [], + create(context) { + return { + Literal(node) { + if (typeof node.value !== 'string') return; + + if (/\$\{/.test(node.value)) { + context.report({ + messageId: 'useBackticks', + node, + fix: (fixer) => fixer.replaceText(node, `\`${node.value}\``), + }); + } + }, + }; + }, +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-json-parse-json-stringify.test.ts b/packages/@n8n/eslint-config/src/rules/no-json-parse-json-stringify.test.ts new file mode 100644 index 0000000000..dabc1b49e5 --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-json-parse-json-stringify.test.ts @@ -0,0 +1,34 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; +import { NoJsonParseJsonStringifyRule } from './no-json-parse-json-stringify.js'; + +const ruleTester = new RuleTester(); + +ruleTester.run('no-json-parse-json-stringify', NoJsonParseJsonStringifyRule, { + valid: [ + { + code: 'deepCopy(foo)', + }, + ], + invalid: [ + { + code: 'JSON.parse(JSON.stringify(foo))', + errors: [{ messageId: 'noJsonParseJsonStringify' }], + output: 'deepCopy(foo)', + }, + { + code: 'JSON.parse(JSON.stringify(foo.bar))', + errors: [{ messageId: 'noJsonParseJsonStringify' }], + output: 'deepCopy(foo.bar)', + }, + { + code: 'JSON.parse(JSON.stringify(foo.bar.baz))', + errors: [{ messageId: 'noJsonParseJsonStringify' }], + output: 'deepCopy(foo.bar.baz)', + }, + { + code: 'JSON.parse(JSON.stringify(foo.bar[baz]))', + errors: [{ messageId: 'noJsonParseJsonStringify' }], + output: 'deepCopy(foo.bar[baz])', + }, + ], +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-json-parse-json-stringify.ts b/packages/@n8n/eslint-config/src/rules/no-json-parse-json-stringify.ts new file mode 100644 index 0000000000..d3d9826b5f --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-json-parse-json-stringify.ts @@ -0,0 +1,48 @@ +import { isJsonParseCall, isJsonStringifyCall } from '../utils/json.js'; +import { ESLintUtils, TSESTree } from '@typescript-eslint/utils'; + +export const NoJsonParseJsonStringifyRule = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + type: 'problem', + docs: { + description: + 'Calls to `JSON.parse(JSON.stringify(arg))` must be replaced with `deepCopy(arg)` from `n8n-workflow`.', + }, + schema: [], + messages: { + noJsonParseJsonStringify: 'Replace with `deepCopy({{ argText }})`', + }, + fixable: 'code', + }, + defaultOptions: [], + create(context) { + return { + CallExpression(node) { + if (isJsonParseCall(node) && isJsonStringifyCall(node)) { + const [callExpression] = node.arguments; + + if (callExpression.type !== TSESTree.AST_NODE_TYPES.CallExpression) { + return; + } + + const { arguments: args } = callExpression; + + if (!Array.isArray(args) || args.length !== 1) return; + + const [arg] = args; + + if (!arg) return; + + const argText = context.sourceCode.getText(arg); + + context.report({ + messageId: 'noJsonParseJsonStringify', + node, + data: { argText }, + fix: (fixer) => fixer.replaceText(node, `deepCopy(${argText})`), + }); + } + }, + }; + }, +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-plain-errors.ts b/packages/@n8n/eslint-config/src/rules/no-plain-errors.ts new file mode 100644 index 0000000000..2e1bc06bd7 --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-plain-errors.ts @@ -0,0 +1,49 @@ +import { ESLintUtils, TSESTree } from '@typescript-eslint/utils'; + +export const NoPlainErrorsRule = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + type: 'problem', + docs: { + description: + 'Only `ApplicationError` (from the `workflow` package) or its child classes must be thrown. This ensures the error will be normalized when reported to Sentry, if applicable.', + }, + messages: { + useApplicationError: + 'Throw an `ApplicationError` (from the `workflow` package) or its child classes.', + }, + fixable: 'code', + schema: [], + }, + defaultOptions: [], + create(context) { + return { + ThrowStatement(node) { + if (!node.argument) return; + + const isNewError = + node.argument.type === TSESTree.AST_NODE_TYPES.NewExpression && + node.argument.callee.type === TSESTree.AST_NODE_TYPES.Identifier && + node.argument.callee.name === 'Error'; + + const isNewlessError = + node.argument.type === TSESTree.AST_NODE_TYPES.CallExpression && + node.argument.callee.type === TSESTree.AST_NODE_TYPES.Identifier && + node.argument.callee.name === 'Error'; + + if (isNewError || isNewlessError) { + return context.report({ + messageId: 'useApplicationError', + node, + fix: (fixer) => + fixer.replaceText( + node, + `throw new ApplicationError(${(node.argument as TSESTree.CallExpression).arguments + .map((arg) => context.sourceCode.getText(arg)) + .join(', ')})`, + ), + }); + } + }, + }; + }, +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-skipped-tests.ts b/packages/@n8n/eslint-config/src/rules/no-skipped-tests.ts new file mode 100644 index 0000000000..912c3b106b --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-skipped-tests.ts @@ -0,0 +1,57 @@ +import { ESLintUtils } from '@typescript-eslint/utils'; + +export const NoSkippedTestsRule = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + type: 'problem', + docs: { + description: 'Tests must not be skipped.', + }, + messages: { + removeSkip: 'Remove `.skip()` call', + removeOnly: 'Remove `.only()` call', + removeXPrefix: 'Remove `x` prefix', + }, + fixable: 'code', + schema: [], + }, + defaultOptions: [], + create(context) { + const TESTING_FUNCTIONS = new Set(['test', 'it', 'describe']); + const SKIPPING_METHODS = new Set(['skip', 'only']); + const PREFIXED_TESTING_FUNCTIONS = new Set(['xtest', 'xit', 'xdescribe']); + const toMessageId = (s: string) => + ('remove' + s.charAt(0).toUpperCase() + s.slice(1)) as + | 'removeSkip' + | 'removeOnly' + | 'removeXPrefix'; + + return { + MemberExpression(node) { + if ( + node.object.type === 'Identifier' && + TESTING_FUNCTIONS.has(node.object.name) && + node.property.type === 'Identifier' && + SKIPPING_METHODS.has(node.property.name) + ) { + context.report({ + messageId: toMessageId(node.property.name), + node, + fix: (fixer) => { + const [start, end] = node.property.range; + return fixer.removeRange([start - '.'.length, end]); + }, + }); + } + }, + CallExpression(node) { + if (node.callee.type === 'Identifier' && PREFIXED_TESTING_FUNCTIONS.has(node.callee.name)) { + context.report({ + messageId: 'removeXPrefix', + node, + fix: (fixer) => fixer.replaceText(node.callee, 'test'), + }); + } + }, + }; + }, +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-type-unsafe-event-emitter.ts b/packages/@n8n/eslint-config/src/rules/no-type-unsafe-event-emitter.ts new file mode 100644 index 0000000000..c55a9e32f5 --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-type-unsafe-event-emitter.ts @@ -0,0 +1,32 @@ +import { ESLintUtils } from '@typescript-eslint/utils'; + +export const NoTypeUnsafeEventEmitterRule = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + type: 'problem', + docs: { + description: 'Disallow extending from `EventEmitter`, which is not type-safe.', + }, + messages: { + noExtendsEventEmitter: 'Extend from the type-safe `TypedEmitter` class instead.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + return { + ClassDeclaration(node) { + if ( + node.superClass && + node.superClass.type === 'Identifier' && + node.superClass.name === 'EventEmitter' && + node.id?.name !== 'TypedEmitter' + ) { + context.report({ + node: node.superClass, + messageId: 'noExtendsEventEmitter', + }); + } + }, + }; + }, +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-uncaught-json-parse.test.ts b/packages/@n8n/eslint-config/src/rules/no-uncaught-json-parse.test.ts new file mode 100644 index 0000000000..24c0cf924f --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-uncaught-json-parse.test.ts @@ -0,0 +1,21 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; +import { NoUncaughtJsonParseRule } from './no-uncaught-json-parse.js'; + +const ruleTester = new RuleTester(); + +ruleTester.run('no-uncaught-json-parse', NoUncaughtJsonParseRule, { + valid: [ + { + code: 'try { JSON.parse(foo) } catch (e) {}', + }, + { + code: 'JSON.parse(JSON.stringify(foo))', + }, + ], + invalid: [ + { + code: 'JSON.parse(foo)', + errors: [{ messageId: 'noUncaughtJsonParse' }], + }, + ], +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-uncaught-json-parse.ts b/packages/@n8n/eslint-config/src/rules/no-uncaught-json-parse.ts new file mode 100644 index 0000000000..b02b3dcf13 --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-uncaught-json-parse.ts @@ -0,0 +1,44 @@ +import { ESLintUtils } from '@typescript-eslint/utils'; +import { isJsonParseCall, isJsonStringifyCall } from '../utils/json.js'; + +export const NoUncaughtJsonParseRule = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + type: 'problem', + hasSuggestions: true, + docs: { + description: + 'Calls to `JSON.parse()` must be replaced with `jsonParse()` from `n8n-workflow` or surrounded with a try/catch block.', + }, + schema: [], + messages: { + noUncaughtJsonParse: + 'Use `jsonParse()` from `n8n-workflow` or surround the `JSON.parse()` call with a try/catch block.', + }, + }, + defaultOptions: [], + create({ report, sourceCode }) { + return { + CallExpression(node) { + if (!isJsonParseCall(node)) { + return; + } + + if (isJsonStringifyCall(node)) { + return; + } + + if ( + sourceCode.getAncestors(node).find((node) => node.type === 'TryStatement') !== undefined + ) { + return; + } + + // Found a JSON.parse() call not wrapped into a try/catch, so report it + report({ + messageId: 'noUncaughtJsonParse', + node, + }); + }, + }; + }, +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-unneeded-backticks.ts b/packages/@n8n/eslint-config/src/rules/no-unneeded-backticks.ts new file mode 100644 index 0000000000..91a0a9ce1e --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-unneeded-backticks.ts @@ -0,0 +1,35 @@ +import { ESLintUtils } from '@typescript-eslint/utils'; + +export const NoUnneededBackticksRule = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + type: 'problem', + docs: { + description: + 'Template literal backticks may only be used for string interpolation or multiline strings.', + }, + messages: { + noUnneededBackticks: 'Use single or double quotes, not backticks', + }, + fixable: 'code', + schema: [], + }, + defaultOptions: [], + create(context) { + return { + TemplateLiteral(node) { + if (node.expressions.length > 0) return; + if (node.quasis.every((q) => q.loc.start.line !== q.loc.end.line)) return; + + node.quasis.forEach((q) => { + const escaped = q.value.raw.replace(/(? fixer.replaceText(q, `'${escaped}'`), + }); + }); + }, + }; + }, +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-untyped-config-class-field.ts b/packages/@n8n/eslint-config/src/rules/no-untyped-config-class-field.ts new file mode 100644 index 0000000000..37c9fe2bdb --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-untyped-config-class-field.ts @@ -0,0 +1,25 @@ +import { ESLintUtils } from '@typescript-eslint/utils'; + +export const NoUntypedConfigClassFieldRule = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + type: 'problem', + docs: { + description: 'Enforce explicit typing of config class fields', + }, + messages: { + noUntypedConfigClassField: + 'Class field must have an explicit type annotation, e.g. `field: type = value`. See: https://github.com/n8n-io/n8n/pull/10433', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + return { + PropertyDefinition(node) { + if (!node.typeAnnotation) { + context.report({ node: node.key, messageId: 'noUntypedConfigClassField' }); + } + }, + }; + }, +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-unused-param-catch-clause.ts b/packages/@n8n/eslint-config/src/rules/no-unused-param-catch-clause.ts new file mode 100644 index 0000000000..9b616893d8 --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-unused-param-catch-clause.ts @@ -0,0 +1,32 @@ +import { ESLintUtils } from '@typescript-eslint/utils'; + +export const NoUnusedParamInCatchClauseRule = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + type: 'problem', + docs: { + description: 'Unused param in catch clause must be omitted.', + }, + messages: { + removeUnusedParam: 'Remove unused param in catch clause', + }, + fixable: 'code', + schema: [], + }, + defaultOptions: [], + create(context) { + return { + CatchClause(node) { + if (node.param?.type === 'Identifier' && node.param.name.startsWith('_')) { + const start = node.range[0] + 'catch '.length; + const end = node.param.range[1] + '()'.length; + + context.report({ + messageId: 'removeUnusedParam', + node, + fix: (fixer) => fixer.removeRange([start, end]), + }); + } + }, + }; + }, +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-useless-catch-throw.test.ts b/packages/@n8n/eslint-config/src/rules/no-useless-catch-throw.test.ts new file mode 100644 index 0000000000..fd60dd55bf --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-useless-catch-throw.test.ts @@ -0,0 +1,34 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; +import { NoUselessCatchThrowRule } from './no-useless-catch-throw.js'; + +const ruleTester = new RuleTester(); + +ruleTester.run('no-useless-catch-throw', NoUselessCatchThrowRule, { + valid: [ + { + code: 'try { foo(); } catch (e) { console.error(e); }', + }, + { + code: 'try { foo(); } catch (e) { throw new Error("Custom error"); }', + }, + ], + invalid: [ + { + code: ` +try { + // Some comment + if (foo) { + bar(); + } +} catch (e) { + throw e; +}`, + errors: [{ messageId: 'noUselessCatchThrow' }], + output: ` +// Some comment +if (foo) { + bar(); +}`, + }, + ], +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-useless-catch-throw.ts b/packages/@n8n/eslint-config/src/rules/no-useless-catch-throw.ts new file mode 100644 index 0000000000..0eb8677945 --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-useless-catch-throw.ts @@ -0,0 +1,46 @@ +import { ESLintUtils } from '@typescript-eslint/utils'; + +export const NoUselessCatchThrowRule = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + type: 'problem', + docs: { + description: 'Disallow `try-catch` blocks where the `catch` only contains a `throw error`.', + }, + messages: { + noUselessCatchThrow: 'Remove useless `catch` block.', + }, + fixable: 'code', + schema: [], + }, + defaultOptions: [], + create(context) { + return { + CatchClause(node) { + if ( + node.body.body.length === 1 && + node.body.body[0].type === 'ThrowStatement' && + node.body.body[0].argument.type === 'Identifier' && + node.param?.type === 'Identifier' && + node.body.body[0].argument.name === node.param.name + ) { + context.report({ + node, + messageId: 'noUselessCatchThrow', + fix(fixer) { + const tryStatement = node.parent; + const tryBlock = tryStatement.block; + const sourceCode = context.sourceCode; + const tryBlockText = sourceCode.getText(tryBlock); + const tryBlockTextWithoutBraces = tryBlockText.slice(1, -1).trim(); + const indentedTryBlockText = tryBlockTextWithoutBraces + .split('\n') + .map((line) => line.replace(/\t/, '')) + .join('\n'); + return fixer.replaceText(tryStatement, indentedTryBlockText); + }, + }); + } + }, + }; + }, +}); diff --git a/packages/@n8n/eslint-config/src/utils/json.ts b/packages/@n8n/eslint-config/src/utils/json.ts new file mode 100644 index 0000000000..2c2d2fd325 --- /dev/null +++ b/packages/@n8n/eslint-config/src/utils/json.ts @@ -0,0 +1,21 @@ +import type { TSESTree } from '@typescript-eslint/utils'; + +export const isJsonParseCall = (node: TSESTree.CallExpression) => + node.callee.type === 'MemberExpression' && + node.callee.object.type === 'Identifier' && + node.callee.object.name === 'JSON' && + node.callee.property.type === 'Identifier' && + node.callee.property.name === 'parse'; + +export const isJsonStringifyCall = (node: TSESTree.CallExpression) => { + const parseArg = node.arguments?.[0]; + return ( + parseArg !== undefined && + parseArg.type === 'CallExpression' && + parseArg.callee.type === 'MemberExpression' && + parseArg.callee.object.type === 'Identifier' && + parseArg.callee.object.name === 'JSON' && + parseArg.callee.property.type === 'Identifier' && + parseArg.callee.property.name === 'stringify' + ); +}; diff --git a/packages/@n8n/eslint-config/tsconfig.json b/packages/@n8n/eslint-config/tsconfig.json new file mode 100644 index 0000000000..c2b1a68ab7 --- /dev/null +++ b/packages/@n8n/eslint-config/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@n8n/typescript-config/tsconfig.common.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "src", + "outDir": "dist", + "types": ["vitest/globals"], + "esModuleInterop": true, + "module": "NodeNext", + "moduleResolution": "nodenext" + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/@n8n/eslint-config/vite.config.ts b/packages/@n8n/eslint-config/vite.config.ts new file mode 100644 index 0000000000..a8de5b43fd --- /dev/null +++ b/packages/@n8n/eslint-config/vite.config.ts @@ -0,0 +1,4 @@ +import { defineConfig, mergeConfig } from 'vite'; +import { vitestConfig } from '@n8n/vitest-config/node'; + +export default mergeConfig(defineConfig({}), vitestConfig); diff --git a/packages/@n8n/extension-sdk/eslint.config.mjs b/packages/@n8n/extension-sdk/eslint.config.mjs new file mode 100644 index 0000000000..eb7a5b54b8 --- /dev/null +++ b/packages/@n8n/extension-sdk/eslint.config.mjs @@ -0,0 +1,14 @@ +import { defineConfig } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; + +export default defineConfig(baseConfig, { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + + // TODO: Remove this + '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'warn', + '@typescript-eslint/prefer-nullish-coalescing': 'warn', + '@typescript-eslint/no-floating-promises': 'warn', + 'import-x/order': 'warn', + }, +}); diff --git a/packages/@n8n/extension-sdk/package.json b/packages/@n8n/extension-sdk/package.json index 9943ccd695..c605118b62 100644 --- a/packages/@n8n/extension-sdk/package.json +++ b/packages/@n8n/extension-sdk/package.json @@ -32,6 +32,7 @@ "scripts": { "clean": "rimraf dist", "dev": "tsup --watch", + "lint": "eslint . --quiet", "typecheck:frontend": "vue-tsc --noEmit --project tsconfig.frontend.json", "typecheck:backend": "tsc --noEmit --project tsconfig.backend.json", "build": "pnpm \"/^typecheck:.+/\" && pnpm clean && tsup && pnpm create-json-schema", @@ -47,7 +48,7 @@ "@vitejs/plugin-vue": "catalog:frontend", "@vue/tsconfig": "catalog:frontend", "rimraf": "catalog:", - "vite": "catalog:frontend", + "vite": "catalog:", "vue": "catalog:frontend", "vue-router": "catalog:frontend", "vue-tsc": "catalog:frontend", diff --git a/packages/@n8n/imap/.eslintrc.js b/packages/@n8n/imap/.eslintrc.js deleted file mode 100644 index 0166cd27be..0000000000 --- a/packages/@n8n/imap/.eslintrc.js +++ /dev/null @@ -1,16 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/base'], - - ...sharedOptions(__dirname), - - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - '@typescript-eslint/consistent-type-imports': 'error', - 'n8n-local-rules/no-plain-errors': 'off', - }, -}; diff --git a/packages/@n8n/imap/eslint.config.mjs b/packages/@n8n/imap/eslint.config.mjs new file mode 100644 index 0000000000..32e61ff125 --- /dev/null +++ b/packages/@n8n/imap/eslint.config.mjs @@ -0,0 +1,16 @@ +import { defineConfig } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; + +export default defineConfig(baseConfig, { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + '@typescript-eslint/consistent-type-imports': 'error', + 'n8n-local-rules/no-plain-errors': 'off', + + // TODO: Remove this + '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'warn', + '@typescript-eslint/prefer-nullish-coalescing': 'warn', + '@typescript-eslint/no-floating-promises': 'warn', + 'import-x/order': 'warn', + }, +}); diff --git a/packages/@n8n/json-schema-to-zod/.eslintrc.js b/packages/@n8n/json-schema-to-zod/.eslintrc.js deleted file mode 100644 index 039086a8bc..0000000000 --- a/packages/@n8n/json-schema-to-zod/.eslintrc.js +++ /dev/null @@ -1,21 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/node'], - - ...sharedOptions(__dirname), - - ignorePatterns: ['jest.config.js'], - - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - '@typescript-eslint/no-duplicate-imports': 'off', - 'import/no-cycle': 'off', - 'n8n-local-rules/no-plain-errors': 'off', - - complexity: 'error', - }, -}; diff --git a/packages/@n8n/json-schema-to-zod/eslint.config.mjs b/packages/@n8n/json-schema-to-zod/eslint.config.mjs new file mode 100644 index 0000000000..2f9e551df4 --- /dev/null +++ b/packages/@n8n/json-schema-to-zod/eslint.config.mjs @@ -0,0 +1,26 @@ +import { defineConfig } from 'eslint/config'; +import { nodeConfig } from '@n8n/eslint-config/node'; + +export default defineConfig( + nodeConfig, + { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + '@typescript-eslint/no-duplicate-imports': 'off', + 'import-x/no-cycle': 'off', + complexity: 'error', + + // TODO: Remove this + 'no-constant-condition': 'warn', + }, + }, + { + files: ['**/*.test.ts'], + rules: { + '@typescript-eslint/no-unused-expressions': 'warn', + '@typescript-eslint/naming-convention': 'warn', + '@typescript-eslint/no-unsafe-assignment': 'warn', + '@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': true }], + }, + }, +); diff --git a/packages/@n8n/json-schema-to-zod/package.json b/packages/@n8n/json-schema-to-zod/package.json index 44cec8ccb1..e68dcedb5c 100644 --- a/packages/@n8n/json-schema-to-zod/package.json +++ b/packages/@n8n/json-schema-to-zod/package.json @@ -21,11 +21,11 @@ "dev": "tsc -w", "format": "biome format --write src", "format:check": "biome ci src", - "lint": "eslint . --quiet", - "lintfix": "eslint . --fix", + "lint": "eslint src --quiet", + "lintfix": "eslint src --fix", "build:types": "tsc -p tsconfig.types.json", - "build:cjs": "tsc -p tsconfig.cjs.json && node postcjs.js", - "build:esm": "tsc -p tsconfig.esm.json && node postesm.js", + "build:cjs": "tsc -p tsconfig.cjs.json && node postcjs.cjs", + "build:esm": "tsc -p tsconfig.esm.json && node postesm.cjs", "build": "rimraf ./dist && pnpm run build:types && pnpm run build:cjs && pnpm run build:esm", "dry": "pnpm run build && pnpm pub --dry-run", "test": "jest", diff --git a/packages/@n8n/json-schema-to-zod/postcjs.js b/packages/@n8n/json-schema-to-zod/postcjs.cjs similarity index 100% rename from packages/@n8n/json-schema-to-zod/postcjs.js rename to packages/@n8n/json-schema-to-zod/postcjs.cjs diff --git a/packages/@n8n/json-schema-to-zod/postesm.js b/packages/@n8n/json-schema-to-zod/postesm.cjs similarity index 100% rename from packages/@n8n/json-schema-to-zod/postesm.js rename to packages/@n8n/json-schema-to-zod/postesm.cjs diff --git a/packages/@n8n/nodes-langchain/.eslintrc.js b/packages/@n8n/nodes-langchain/.eslintrc.js deleted file mode 100644 index 3db00d4dff..0000000000 --- a/packages/@n8n/nodes-langchain/.eslintrc.js +++ /dev/null @@ -1,158 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/node'], - - ...sharedOptions(__dirname), - - ignorePatterns: ['index.js', '**/package.json'], - - rules: { - // TODO: remove all the following rules - eqeqeq: 'warn', - 'id-denylist': 'warn', - 'import/extensions': 'warn', - 'prefer-spread': 'warn', - - '@typescript-eslint/naming-convention': ['error', { selector: 'memberLike', format: null }], - '@typescript-eslint/no-explicit-any': 'warn', //812 warnings, better to fix in separate PR - '@typescript-eslint/no-non-null-assertion': 'warn', //665 errors, better to fix in separate PR - '@typescript-eslint/no-unsafe-assignment': 'warn', //7084 problems, better to fix in separate PR - '@typescript-eslint/no-unsafe-call': 'warn', //541 errors, better to fix in separate PR - '@typescript-eslint/no-unsafe-member-access': 'warn', //4591 errors, better to fix in separate PR - '@typescript-eslint/no-unsafe-return': 'warn', //438 errors, better to fix in separate PR - '@typescript-eslint/no-unused-expressions': ['error', { allowTernary: true }], - '@typescript-eslint/restrict-template-expressions': 'warn', //1152 errors, better to fix in separate PR - '@typescript-eslint/unbound-method': 'warn', - '@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': true }], - '@typescript-eslint/prefer-nullish-coalescing': 'warn', - '@typescript-eslint/no-base-to-string': 'warn', - '@typescript-eslint/no-redundant-type-constituents': 'warn', - '@typescript-eslint/no-unsafe-argument': 'warn', - '@typescript-eslint/prefer-optional-chain': 'warn', - '@typescript-eslint/restrict-plus-operands': 'warn', - }, - - overrides: [ - { - files: ['./credentials/*.ts'], - plugins: ['eslint-plugin-n8n-nodes-base'], - rules: { - 'n8n-nodes-base/cred-class-field-authenticate-type-assertion': 'error', - 'n8n-nodes-base/cred-class-field-display-name-missing-oauth2': 'error', - 'n8n-nodes-base/cred-class-field-display-name-miscased': 'error', - 'n8n-nodes-base/cred-class-field-documentation-url-missing': 'error', - 'n8n-nodes-base/cred-class-field-name-missing-oauth2': 'error', - 'n8n-nodes-base/cred-class-field-name-unsuffixed': 'error', - 'n8n-nodes-base/cred-class-field-name-uppercase-first-char': 'error', - 'n8n-nodes-base/cred-class-field-properties-assertion': 'error', - 'n8n-nodes-base/cred-class-field-type-options-password-missing': 'error', - 'n8n-nodes-base/cred-class-name-missing-oauth2-suffix': 'error', - 'n8n-nodes-base/cred-class-name-unsuffixed': 'error', - 'n8n-nodes-base/cred-filename-against-convention': 'error', - }, - }, - { - files: ['./nodes/**/*.ts'], - plugins: ['eslint-plugin-n8n-nodes-base'], - rules: { - 'n8n-nodes-base/node-class-description-credentials-name-unsuffixed': 'error', - 'n8n-nodes-base/node-class-description-display-name-unsuffixed-trigger-node': 'error', - 'n8n-nodes-base/node-class-description-empty-string': 'error', - 'n8n-nodes-base/node-class-description-icon-not-svg': 'error', - 'n8n-nodes-base/node-class-description-inputs-wrong-regular-node': 'off', - 'n8n-nodes-base/node-class-description-outputs-wrong': 'off', - 'n8n-nodes-base/node-class-description-inputs-wrong-trigger-node': 'error', - 'n8n-nodes-base/node-class-description-missing-subtitle': 'error', - 'n8n-nodes-base/node-class-description-non-core-color-present': 'error', - 'n8n-nodes-base/node-class-description-name-miscased': 'error', - 'n8n-nodes-base/node-class-description-name-unsuffixed-trigger-node': 'error', - 'n8n-nodes-base/node-dirname-against-convention': 'error', - 'n8n-nodes-base/node-execute-block-double-assertion-for-items': 'error', - 'n8n-nodes-base/node-execute-block-wrong-error-thrown': 'error', - 'n8n-nodes-base/node-filename-against-convention': 'error', - 'n8n-nodes-base/node-param-array-type-assertion': 'error', - 'n8n-nodes-base/node-param-color-type-unused': 'error', - 'n8n-nodes-base/node-param-default-missing': 'error', - 'n8n-nodes-base/node-param-default-wrong-for-boolean': 'error', - 'n8n-nodes-base/node-param-default-wrong-for-collection': 'error', - 'n8n-nodes-base/node-param-default-wrong-for-fixed-collection': 'error', - 'n8n-nodes-base/node-param-default-wrong-for-fixed-collection': 'error', - 'n8n-nodes-base/node-param-default-wrong-for-multi-options': 'error', - 'n8n-nodes-base/node-param-default-wrong-for-number': 'error', - 'n8n-nodes-base/node-param-default-wrong-for-simplify': 'error', - 'n8n-nodes-base/node-param-default-wrong-for-string': 'error', - 'n8n-nodes-base/node-param-description-boolean-without-whether': 'error', - 'n8n-nodes-base/node-param-description-comma-separated-hyphen': 'error', - 'n8n-nodes-base/node-param-description-empty-string': 'error', - 'n8n-nodes-base/node-param-description-excess-final-period': 'error', - 'n8n-nodes-base/node-param-description-excess-inner-whitespace': 'error', - 'n8n-nodes-base/node-param-description-identical-to-display-name': 'error', - 'n8n-nodes-base/node-param-description-line-break-html-tag': 'error', - 'n8n-nodes-base/node-param-description-lowercase-first-char': 'error', - 'n8n-nodes-base/node-param-description-miscased-id': 'error', - 'n8n-nodes-base/node-param-description-miscased-json': 'error', - 'n8n-nodes-base/node-param-description-miscased-url': 'error', - 'n8n-nodes-base/node-param-description-missing-final-period': 'error', - 'n8n-nodes-base/node-param-description-missing-for-ignore-ssl-issues': 'error', - 'n8n-nodes-base/node-param-description-missing-for-return-all': 'error', - 'n8n-nodes-base/node-param-description-missing-for-simplify': 'error', - 'n8n-nodes-base/node-param-description-missing-from-dynamic-multi-options': 'error', - 'n8n-nodes-base/node-param-description-missing-from-dynamic-options': 'error', - 'n8n-nodes-base/node-param-description-missing-from-limit': 'error', - 'n8n-nodes-base/node-param-description-unencoded-angle-brackets': 'error', - 'n8n-nodes-base/node-param-description-unneeded-backticks': 'error', - 'n8n-nodes-base/node-param-description-untrimmed': 'error', - 'n8n-nodes-base/node-param-description-url-missing-protocol': 'error', - 'n8n-nodes-base/node-param-description-weak': 'error', - 'n8n-nodes-base/node-param-description-wrong-for-dynamic-multi-options': 'error', - 'n8n-nodes-base/node-param-description-wrong-for-dynamic-options': 'error', - 'n8n-nodes-base/node-param-description-wrong-for-ignore-ssl-issues': 'error', - 'n8n-nodes-base/node-param-description-wrong-for-limit': 'error', - 'n8n-nodes-base/node-param-description-wrong-for-return-all': 'error', - 'n8n-nodes-base/node-param-description-wrong-for-simplify': 'error', - 'n8n-nodes-base/node-param-description-wrong-for-upsert': 'error', - 'n8n-nodes-base/node-param-display-name-excess-inner-whitespace': 'error', - 'n8n-nodes-base/node-param-display-name-miscased-id': 'error', - 'n8n-nodes-base/node-param-display-name-miscased': 'error', - 'n8n-nodes-base/node-param-display-name-not-first-position': 'error', - 'n8n-nodes-base/node-param-display-name-untrimmed': 'error', - 'n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options': 'error', - 'n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options': 'error', - 'n8n-nodes-base/node-param-display-name-wrong-for-simplify': 'error', - 'n8n-nodes-base/node-param-display-name-wrong-for-update-fields': 'error', - 'n8n-nodes-base/node-param-min-value-wrong-for-limit': 'error', - 'n8n-nodes-base/node-param-multi-options-type-unsorted-items': 'error', - 'n8n-nodes-base/node-param-name-untrimmed': 'error', - 'n8n-nodes-base/node-param-operation-option-action-wrong-for-get-many': 'error', - 'n8n-nodes-base/node-param-operation-option-description-wrong-for-get-many': 'error', - 'n8n-nodes-base/node-param-operation-option-without-action': 'error', - 'n8n-nodes-base/node-param-operation-without-no-data-expression': 'error', - 'n8n-nodes-base/node-param-option-description-identical-to-name': 'error', - 'n8n-nodes-base/node-param-option-name-containing-star': 'error', - 'n8n-nodes-base/node-param-option-name-duplicate': 'error', - 'n8n-nodes-base/node-param-option-name-wrong-for-get-many': 'error', - 'n8n-nodes-base/node-param-option-name-wrong-for-upsert': 'error', - 'n8n-nodes-base/node-param-option-value-duplicate': 'error', - 'n8n-nodes-base/node-param-options-type-unsorted-items': 'error', - 'n8n-nodes-base/node-param-placeholder-miscased-id': 'error', - 'n8n-nodes-base/node-param-placeholder-missing-email': 'error', - 'n8n-nodes-base/node-param-required-false': 'error', - 'n8n-nodes-base/node-param-resource-with-plural-option': 'error', - 'n8n-nodes-base/node-param-resource-without-no-data-expression': 'error', - 'n8n-nodes-base/node-param-type-options-missing-from-limit': 'error', - 'n8n-nodes-base/node-param-type-options-password-missing': 'error', - }, - }, - { - files: ['**/*.test.ts', '**/test/**/*.ts'], - rules: { - 'import/no-extraneous-dependencies': 'off', - 'n8n-nodes-base/node-filename-against-convention': 'off', - }, - }, - ], -}; diff --git a/packages/@n8n/nodes-langchain/eslint.config.mjs b/packages/@n8n/nodes-langchain/eslint.config.mjs new file mode 100644 index 0000000000..e589ba31a4 --- /dev/null +++ b/packages/@n8n/nodes-langchain/eslint.config.mjs @@ -0,0 +1,164 @@ +import { defineConfig } from 'eslint/config'; +import { nodeConfig } from '@n8n/eslint-config/node'; +import nodesBasePlugin from 'eslint-plugin-n8n-nodes-base'; + +export default defineConfig( + nodeConfig, + { + rules: { + // TODO: remove all the following rules + eqeqeq: 'warn', + 'id-denylist': 'warn', + 'import-x/extensions': 'warn', + 'prefer-spread': 'warn', + 'no-case-declarations': 'warn', + 'no-extra-boolean-cast': 'warn', + 'no-empty': 'warn', + 'no-prototype-builtins': 'warn', + 'import-x/order': 'warn', + '@typescript-eslint/no-unnecessary-type-assertion': 'warn', + 'no-async-promise-executor': 'warn', + 'no-useless-escape': 'warn', + + '@typescript-eslint/naming-convention': ['error', { selector: 'memberLike', format: null }], + '@typescript-eslint/no-explicit-any': 'warn', //812 warnings, better to fix in separate PR + '@typescript-eslint/no-non-null-assertion': 'warn', //665 errors, better to fix in separate PR + '@typescript-eslint/no-unsafe-assignment': 'warn', //7084 problems, better to fix in separate PR + '@typescript-eslint/no-unsafe-call': 'warn', //541 errors, better to fix in separate PR + '@typescript-eslint/no-unsafe-member-access': 'warn', //4591 errors, better to fix in separate PR + '@typescript-eslint/no-unsafe-return': 'warn', //438 errors, better to fix in separate PR + '@typescript-eslint/no-unused-expressions': ['error', { allowTernary: true }], + '@typescript-eslint/restrict-template-expressions': 'warn', //1152 errors, better to fix in separate PR + '@typescript-eslint/unbound-method': 'warn', + '@typescript-eslint/prefer-nullish-coalescing': 'warn', + '@typescript-eslint/no-base-to-string': 'warn', + '@typescript-eslint/no-redundant-type-constituents': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + '@typescript-eslint/prefer-optional-chain': 'warn', + '@typescript-eslint/restrict-plus-operands': 'warn', + '@typescript-eslint/no-duplicate-type-constituents': 'warn', + '@typescript-eslint/require-await': 'warn', + }, + }, + { + files: ['./credentials/*.ts'], + plugins: { + 'n8n-nodes-base': nodesBasePlugin, + }, + rules: { + 'n8n-nodes-base/cred-class-field-authenticate-type-assertion': 'error', + 'n8n-nodes-base/cred-class-field-display-name-missing-oauth2': 'error', + 'n8n-nodes-base/cred-class-field-display-name-miscased': 'error', + 'n8n-nodes-base/cred-class-field-documentation-url-missing': 'error', + 'n8n-nodes-base/cred-class-field-name-missing-oauth2': 'error', + 'n8n-nodes-base/cred-class-field-name-unsuffixed': 'error', + 'n8n-nodes-base/cred-class-field-name-uppercase-first-char': 'error', + 'n8n-nodes-base/cred-class-field-properties-assertion': 'error', + 'n8n-nodes-base/cred-class-field-type-options-password-missing': 'error', + 'n8n-nodes-base/cred-class-name-missing-oauth2-suffix': 'error', + 'n8n-nodes-base/cred-class-name-unsuffixed': 'error', + 'n8n-nodes-base/cred-filename-against-convention': 'error', + }, + }, + { + files: ['./nodes/**/*.ts'], + plugins: { + 'n8n-nodes-base': nodesBasePlugin, + }, + rules: { + 'n8n-nodes-base/node-class-description-credentials-name-unsuffixed': 'error', + 'n8n-nodes-base/node-class-description-display-name-unsuffixed-trigger-node': 'error', + 'n8n-nodes-base/node-class-description-empty-string': 'error', + 'n8n-nodes-base/node-class-description-icon-not-svg': 'error', + 'n8n-nodes-base/node-class-description-inputs-wrong-regular-node': 'off', + 'n8n-nodes-base/node-class-description-outputs-wrong': 'off', + 'n8n-nodes-base/node-class-description-inputs-wrong-trigger-node': 'error', + 'n8n-nodes-base/node-class-description-missing-subtitle': 'error', + 'n8n-nodes-base/node-class-description-non-core-color-present': 'error', + 'n8n-nodes-base/node-class-description-name-miscased': 'error', + 'n8n-nodes-base/node-class-description-name-unsuffixed-trigger-node': 'error', + 'n8n-nodes-base/node-dirname-against-convention': 'error', + 'n8n-nodes-base/node-execute-block-double-assertion-for-items': 'error', + 'n8n-nodes-base/node-execute-block-wrong-error-thrown': 'error', + 'n8n-nodes-base/node-filename-against-convention': 'error', + 'n8n-nodes-base/node-param-array-type-assertion': 'error', + 'n8n-nodes-base/node-param-color-type-unused': 'error', + 'n8n-nodes-base/node-param-default-missing': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-boolean': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-collection': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-fixed-collection': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-fixed-collection': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-multi-options': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-number': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-simplify': 'error', + 'n8n-nodes-base/node-param-default-wrong-for-string': 'error', + 'n8n-nodes-base/node-param-description-boolean-without-whether': 'error', + 'n8n-nodes-base/node-param-description-comma-separated-hyphen': 'error', + 'n8n-nodes-base/node-param-description-empty-string': 'error', + 'n8n-nodes-base/node-param-description-excess-final-period': 'error', + 'n8n-nodes-base/node-param-description-excess-inner-whitespace': 'error', + 'n8n-nodes-base/node-param-description-identical-to-display-name': 'error', + 'n8n-nodes-base/node-param-description-line-break-html-tag': 'error', + 'n8n-nodes-base/node-param-description-lowercase-first-char': 'error', + 'n8n-nodes-base/node-param-description-miscased-id': 'error', + 'n8n-nodes-base/node-param-description-miscased-json': 'error', + 'n8n-nodes-base/node-param-description-miscased-url': 'error', + 'n8n-nodes-base/node-param-description-missing-final-period': 'error', + 'n8n-nodes-base/node-param-description-missing-for-ignore-ssl-issues': 'error', + 'n8n-nodes-base/node-param-description-missing-for-return-all': 'error', + 'n8n-nodes-base/node-param-description-missing-for-simplify': 'error', + 'n8n-nodes-base/node-param-description-missing-from-dynamic-multi-options': 'error', + 'n8n-nodes-base/node-param-description-missing-from-dynamic-options': 'error', + 'n8n-nodes-base/node-param-description-missing-from-limit': 'error', + 'n8n-nodes-base/node-param-description-unencoded-angle-brackets': 'error', + 'n8n-nodes-base/node-param-description-unneeded-backticks': 'error', + 'n8n-nodes-base/node-param-description-untrimmed': 'error', + 'n8n-nodes-base/node-param-description-url-missing-protocol': 'error', + 'n8n-nodes-base/node-param-description-weak': 'error', + 'n8n-nodes-base/node-param-description-wrong-for-dynamic-multi-options': 'error', + 'n8n-nodes-base/node-param-description-wrong-for-dynamic-options': 'error', + 'n8n-nodes-base/node-param-description-wrong-for-ignore-ssl-issues': 'error', + 'n8n-nodes-base/node-param-description-wrong-for-limit': 'error', + 'n8n-nodes-base/node-param-description-wrong-for-return-all': 'error', + 'n8n-nodes-base/node-param-description-wrong-for-simplify': 'error', + 'n8n-nodes-base/node-param-description-wrong-for-upsert': 'error', + 'n8n-nodes-base/node-param-display-name-excess-inner-whitespace': 'error', + 'n8n-nodes-base/node-param-display-name-miscased-id': 'error', + 'n8n-nodes-base/node-param-display-name-miscased': 'error', + 'n8n-nodes-base/node-param-display-name-not-first-position': 'error', + 'n8n-nodes-base/node-param-display-name-untrimmed': 'error', + 'n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options': 'error', + 'n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options': 'error', + 'n8n-nodes-base/node-param-display-name-wrong-for-simplify': 'error', + 'n8n-nodes-base/node-param-display-name-wrong-for-update-fields': 'error', + 'n8n-nodes-base/node-param-min-value-wrong-for-limit': 'error', + 'n8n-nodes-base/node-param-multi-options-type-unsorted-items': 'error', + 'n8n-nodes-base/node-param-name-untrimmed': 'error', + 'n8n-nodes-base/node-param-operation-option-action-wrong-for-get-many': 'error', + 'n8n-nodes-base/node-param-operation-option-description-wrong-for-get-many': 'error', + 'n8n-nodes-base/node-param-operation-option-without-action': 'error', + 'n8n-nodes-base/node-param-operation-without-no-data-expression': 'error', + 'n8n-nodes-base/node-param-option-description-identical-to-name': 'error', + 'n8n-nodes-base/node-param-option-name-containing-star': 'error', + 'n8n-nodes-base/node-param-option-name-duplicate': 'error', + 'n8n-nodes-base/node-param-option-name-wrong-for-get-many': 'error', + 'n8n-nodes-base/node-param-option-name-wrong-for-upsert': 'error', + 'n8n-nodes-base/node-param-option-value-duplicate': 'error', + 'n8n-nodes-base/node-param-options-type-unsorted-items': 'error', + 'n8n-nodes-base/node-param-placeholder-miscased-id': 'error', + 'n8n-nodes-base/node-param-placeholder-missing-email': 'error', + 'n8n-nodes-base/node-param-required-false': 'error', + 'n8n-nodes-base/node-param-resource-with-plural-option': 'error', + 'n8n-nodes-base/node-param-resource-without-no-data-expression': 'error', + 'n8n-nodes-base/node-param-type-options-missing-from-limit': 'error', + 'n8n-nodes-base/node-param-type-options-password-missing': 'error', + }, + }, + { + files: ['**/*.test.ts', '**/test/**/*.ts', '**/__test__/**/*.ts', '**/__tests__/**/*.ts'], + rules: { + 'import-x/no-extraneous-dependencies': 'warn', + 'n8n-local-rules/no-uncaught-json-parse': 'warn', + }, + }, +); diff --git a/packages/@n8n/nodes-langchain/package.json b/packages/@n8n/nodes-langchain/package.json index 9e64d5c7a5..0b42692f14 100644 --- a/packages/@n8n/nodes-langchain/package.json +++ b/packages/@n8n/nodes-langchain/package.json @@ -12,7 +12,7 @@ "build": "tsup --tsconfig tsconfig.build.json && pnpm copy-nodes-json && tsc-alias -p tsconfig.build.json && pnpm copy-tokenizer-json && pnpm n8n-copy-static-files && pnpm n8n-generate-metadata", "format": "biome format --write .", "format:check": "biome ci .", - "lint": "eslint nodes credentials utils --quiet", + "lint": "eslint nodes credentials utils", "lintfix": "eslint nodes credentials utils --fix", "watch": "tsup --watch --tsconfig tsconfig.build.json --onSuccess \"pnpm copy-nodes-json && tsc-alias -p tsconfig.build.json && pnpm n8n-generate-metadata\"", "test": "jest", diff --git a/packages/@n8n/nodes-langchain/utils/tests/tiktoken.test.ts b/packages/@n8n/nodes-langchain/utils/tests/tiktoken.test.ts index 4f67c7d255..6c12e5080f 100644 --- a/packages/@n8n/nodes-langchain/utils/tests/tiktoken.test.ts +++ b/packages/@n8n/nodes-langchain/utils/tests/tiktoken.test.ts @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-require-imports */ import type { TiktokenEncoding } from 'js-tiktoken/lite'; import { Tiktoken } from 'js-tiktoken/lite'; diff --git a/packages/@n8n/permissions/.eslintrc.js b/packages/@n8n/permissions/.eslintrc.js deleted file mode 100644 index 96638bbca8..0000000000 --- a/packages/@n8n/permissions/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** @type {import('@types/eslint').ESLint.ConfigData} */ -module.exports = { - extends: ['@n8n/eslint-config/base'], - ...sharedOptions(__dirname), - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - }, -}; diff --git a/packages/@n8n/permissions/eslint.config.mjs b/packages/@n8n/permissions/eslint.config.mjs new file mode 100644 index 0000000000..d1b2e56f96 --- /dev/null +++ b/packages/@n8n/permissions/eslint.config.mjs @@ -0,0 +1,12 @@ +import { defineConfig } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; + +export default defineConfig(baseConfig, { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + + // TODO: Remove this + 'import-x/order': 'warn', + '@typescript-eslint/naming-convention': 'warn', + }, +}); diff --git a/packages/@n8n/task-runner/.eslintrc.js b/packages/@n8n/task-runner/.eslintrc.js deleted file mode 100644 index 2883377f5f..0000000000 --- a/packages/@n8n/task-runner/.eslintrc.js +++ /dev/null @@ -1,19 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/node'], - - ...sharedOptions(__dirname), - - ignorePatterns: ['jest.config.js'], - - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - // '@typescript-eslint/no-duplicate-imports': 'off', - - complexity: 'error', - }, -}; diff --git a/packages/@n8n/task-runner/eslint.config.mjs b/packages/@n8n/task-runner/eslint.config.mjs new file mode 100644 index 0000000000..c9fefe6844 --- /dev/null +++ b/packages/@n8n/task-runner/eslint.config.mjs @@ -0,0 +1,28 @@ +import { defineConfig } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; + +export default defineConfig( + baseConfig, + { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + complexity: 'error', + + // TODO: Remove this + '@typescript-eslint/naming-convention': 'warn', + '@typescript-eslint/no-require-imports': 'warn', + '@typescript-eslint/require-await': 'warn', + }, + }, + { + files: ['**/*.test.ts'], + rules: { + 'n8n-local-rules/no-uncaught-json-parse': 'warn', + 'import-x/no-duplicates': 'warn', + '@typescript-eslint/unbound-method': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + '@typescript-eslint/no-unsafe-member-access': 'warn', + '@typescript-eslint/no-unsafe-assignment': 'warn', + }, + }, +); diff --git a/packages/@n8n/utils/.eslintrc.cjs b/packages/@n8n/utils/.eslintrc.cjs deleted file mode 100644 index 8050caa04e..0000000000 --- a/packages/@n8n/utils/.eslintrc.cjs +++ /dev/null @@ -1,10 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/node'], - - ...sharedOptions(__dirname), -}; diff --git a/packages/@n8n/utils/eslint.config.mjs b/packages/@n8n/utils/eslint.config.mjs new file mode 100644 index 0000000000..efa83e043b --- /dev/null +++ b/packages/@n8n/utils/eslint.config.mjs @@ -0,0 +1,10 @@ +import { defineConfig } from 'eslint/config'; +import { nodeConfig } from '@n8n/eslint-config/node'; + +export default defineConfig(nodeConfig, { + rules: { + // TODO: Remove this + 'no-prototype-builtins': 'warn', + '@typescript-eslint/require-await': 'warn', + }, +}); diff --git a/packages/@n8n/utils/package.json b/packages/@n8n/utils/package.json index 7c5a1d890a..5506709f6e 100644 --- a/packages/@n8n/utils/package.json +++ b/packages/@n8n/utils/package.json @@ -24,8 +24,8 @@ "typecheck": "tsc --noEmit", "test": "vitest run", "test:dev": "vitest --silent=false", - "lint": "eslint src --ext .js,.ts,.vue --quiet", - "lintfix": "eslint src --ext .js,.ts,.vue --fix", + "lint": "eslint src --quiet", + "lintfix": "eslint src --fix", "format": "biome format --write . && prettier --write . --ignore-path ../../../.prettierignore", "format:check": "biome ci . && prettier --check . --ignore-path ../../../.prettierignore" }, @@ -36,9 +36,9 @@ "@testing-library/jest-dom": "catalog:frontend", "@testing-library/user-event": "catalog:frontend", "tsup": "catalog:", - "typescript": "catalog:frontend", - "vite": "catalog:frontend", - "vitest": "catalog:frontend" + "typescript": "catalog:", + "vite": "catalog:", + "vitest": "catalog:" }, "license": "See LICENSE.md file in the root of the repository" } diff --git a/packages/@n8n/utils/src/event-bus.ts b/packages/@n8n/utils/src/event-bus.ts index b25358fec8..7526cf9741 100644 --- a/packages/@n8n/utils/src/event-bus.ts +++ b/packages/@n8n/utils/src/event-bus.ts @@ -1,5 +1,5 @@ -// eslint-disable-next-line @typescript-eslint/ban-types -export type CallbackFn = Function; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type CallbackFn = (...args: any[]) => any; type Payloads = { [E in keyof ListenerMap]: unknown; diff --git a/packages/@n8n/utils/src/search/sublimeSearch.ts b/packages/@n8n/utils/src/search/sublimeSearch.ts index 5746424bec..85167440f5 100644 --- a/packages/@n8n/utils/src/search/sublimeSearch.ts +++ b/packages/@n8n/utils/src/search/sublimeSearch.ts @@ -221,7 +221,7 @@ function getValue(obj: T, prop: string): unknown { export function sublimeSearch( filter: string, - data: Readonly, + data: readonly T[], keys: Array<{ key: string; weight: number }> = DEFAULT_KEYS, ): Array<{ score: number; item: T }> { const results = data.reduce((accu: Array<{ score: number; item: T }>, item: T) => { diff --git a/packages/@n8n/vitest-config/frontend.d.ts b/packages/@n8n/vitest-config/frontend.d.ts deleted file mode 100644 index 39b6396b58..0000000000 --- a/packages/@n8n/vitest-config/frontend.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { InlineConfig } from 'vitest/node'; -import type { UserConfig } from 'vite'; - -export const createVitestConfig: (options?: InlineConfig) => UserConfig; - -export const vitestConfig: UserConfig; diff --git a/packages/@n8n/vitest-config/frontend.mjs b/packages/@n8n/vitest-config/frontend.ts similarity index 61% rename from packages/@n8n/vitest-config/frontend.mjs rename to packages/@n8n/vitest-config/frontend.ts index 5799aceb16..0501deaa7b 100644 --- a/packages/@n8n/vitest-config/frontend.mjs +++ b/packages/@n8n/vitest-config/frontend.ts @@ -1,12 +1,7 @@ -import { defineConfig as defineVitestConfig } from 'vitest/config'; +import { defineConfig } from 'vitest/config'; -/** - * Define a Vitest configuration - * @param {import('vitest/node').InlineConfig} options - The options to pass to the Vitest configuration - * @returns {import('vite').UserConfig} - */ export const createVitestConfig = (options = {}) => { - const vitestConfig = defineVitestConfig({ + const vitestConfig = defineConfig({ test: { silent: true, globals: true, @@ -27,10 +22,10 @@ export const createVitestConfig = (options = {}) => { }, }); - if (process.env.COVERAGE_ENABLED === 'true') { + if (process.env.COVERAGE_ENABLED === 'true' && vitestConfig.test?.coverage) { const { coverage } = vitestConfig.test; coverage.enabled = true; - if (process.env.CI === 'true') { + if (process.env.CI === 'true' && coverage.provider === 'v8') { coverage.all = true; coverage.reporter = ['cobertura']; } diff --git a/packages/@n8n/vitest-config/node.ts b/packages/@n8n/vitest-config/node.ts new file mode 100644 index 0000000000..38fcdf87ab --- /dev/null +++ b/packages/@n8n/vitest-config/node.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'vitest/config'; + +export const vitestConfig = defineConfig({ + test: { + silent: true, + globals: true, + environment: 'node', + ...(process.env.COVERAGE_ENABLED === 'true' + ? { + coverage: { + enabled: true, + provider: 'v8', + reporter: process.env.CI === 'true' ? 'cobertura' : 'text-summary', + all: true, + }, + } + : {}), + }, +}); diff --git a/packages/@n8n/vitest-config/package.json b/packages/@n8n/vitest-config/package.json index 5075b88561..782aaed3f5 100644 --- a/packages/@n8n/vitest-config/package.json +++ b/packages/@n8n/vitest-config/package.json @@ -3,23 +3,37 @@ "version": "1.3.0", "type": "module", "peerDependencies": { - "vite": "catalog:frontend", - "vitest": "catalog:frontend" + "vite": "catalog:", + "vitest": "catalog:" }, "devDependencies": { - "vite": "catalog:frontend", - "vitest": "catalog:frontend" + "@n8n/typescript-config": "workspace:*", + "vite": "catalog:", + "vitest": "catalog:" }, "files": [ "frontend.mjs" ], "exports": { "./frontend": { - "import": "./frontend.mjs", - "require": "./frontend.mjs", - "types": "./frontend.d.ts" + "import": "./dist/frontend.js", + "require": "./dist/frontend.js", + "types": "./dist/frontend.d.ts" }, - "./*": "./*" + "./node": { + "import": "./dist/node.js", + "require": "./dist/node.js", + "types": "./dist/node.d.ts" + } + }, + "scripts": { + "clean": "rimraf dist .turbo", + "dev": "pnpm watch", + "typecheck": "tsc --noEmit", + "build": "tsc -p tsconfig.build.json", + "format": "biome format --write .", + "format:check": "biome ci .", + "watch": "tsc -p tsconfig.build.json --watch" }, "license": "See LICENSE.md file in the root of the repository" } diff --git a/packages/@n8n/vitest-config/tsconfig.build.json b/packages/@n8n/vitest-config/tsconfig.build.json new file mode 100644 index 0000000000..130f68a1d2 --- /dev/null +++ b/packages/@n8n/vitest-config/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"], + "compilerOptions": { + "composite": true, + "rootDir": ".", + "outDir": "dist", + "tsBuildInfoFile": "dist/build.tsbuildinfo" + }, + "include": ["**/*.ts"], + "exclude": ["dist", "**/*.test.ts"] +} diff --git a/packages/@n8n/vitest-config/tsconfig.json b/packages/@n8n/vitest-config/tsconfig.json new file mode 100644 index 0000000000..6c69190b37 --- /dev/null +++ b/packages/@n8n/vitest-config/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@n8n/typescript-config/tsconfig.common.json", + "compilerOptions": { + "rootDir": ".", + "types": ["node"], + "baseUrl": ".", + "module": "ESNext", + "moduleResolution": "bundler", + "tsBuildInfoFile": "dist/typecheck.tsbuildinfo" + }, + "include": ["**/*.ts"], + "exclude": ["dist"] +} diff --git a/packages/cli/.eslintrc.js b/packages/cli/.eslintrc.js deleted file mode 100644 index c79d43caf8..0000000000 --- a/packages/cli/.eslintrc.js +++ /dev/null @@ -1,75 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/node'], - - ...sharedOptions(__dirname), - - parserOptions: { - project: './tsconfig.json', - }, - - ignorePatterns: ['jest.config.js'], - - rules: { - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - - 'n8n-local-rules/no-dynamic-import-template': 'error', - 'n8n-local-rules/misplaced-n8n-typeorm-import': 'error', - 'n8n-local-rules/no-type-unsafe-event-emitter': 'error', - complexity: 'error', - - // TODO: Remove this - 'import/extensions': 'warn', - '@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': true }], - '@typescript-eslint/no-explicit-any': 'warn', - '@typescript-eslint/no-base-to-string': 'warn', - '@typescript-eslint/prefer-nullish-coalescing': 'warn', - '@typescript-eslint/no-redundant-type-constituents': 'warn', - '@typescript-eslint/ban-types': 'warn', - '@typescript-eslint/no-unsafe-enum-comparison': 'warn', - '@typescript-eslint/no-unsafe-declaration-merging': 'warn', - }, - - overrides: [ - { - files: [ - './src/databases/**/*.ts', - './src/modules/**/*.ts', - './test/**/*.ts', - './src/**/__tests__/**/*.ts', - ], - rules: { - 'n8n-local-rules/misplaced-n8n-typeorm-import': 'off', - }, - }, - { - files: ['./test/**/*.ts', './src/**/__tests__/**/*.ts'], - rules: { - 'n8n-local-rules/no-type-unsafe-event-emitter': 'off', - }, - }, - { - files: ['./src/decorators/**/*.ts'], - rules: { - '@typescript-eslint/ban-types': [ - 'warn', - { - types: { - Function: false, - }, - }, - ], - }, - }, - { - files: ['./test/**/*.ts', './src/**/__tests__/**/*.ts'], - rules: { - 'n8n-local-rules/no-dynamic-import-template': 'off', - }, - }, - ], -}; diff --git a/packages/cli/eslint.config.mjs b/packages/cli/eslint.config.mjs new file mode 100644 index 0000000000..a262244213 --- /dev/null +++ b/packages/cli/eslint.config.mjs @@ -0,0 +1,102 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import { nodeConfig } from '@n8n/eslint-config/node'; + +export default defineConfig( + globalIgnores(['scripts/**/*.mjs']), + nodeConfig, + { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + + 'n8n-local-rules/no-dynamic-import-template': 'error', + 'n8n-local-rules/misplaced-n8n-typeorm-import': 'error', + 'n8n-local-rules/no-type-unsafe-event-emitter': 'error', + + // TODO: Remove this + '@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': true }], + 'import-x/no-cycle': 'warn', + 'import-x/extensions': 'warn', + 'import-x/order': 'warn', + 'no-ex-assign': 'warn', + 'no-case-declarations': 'warn', + 'no-fallthrough': 'warn', + 'no-unsafe-optional-chaining': 'warn', + 'no-empty': 'warn', + 'no-async-promise-executor': 'warn', + complexity: 'warn', + '@typescript-eslint/require-await': 'warn', + '@typescript-eslint/no-empty-object-type': 'warn', + '@typescript-eslint/prefer-promise-reject-errors': 'warn', + '@typescript-eslint/no-unsafe-function-type': 'warn', + '@typescript-eslint/naming-convention': 'warn', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-base-to-string': 'warn', + '@typescript-eslint/prefer-nullish-coalescing': 'warn', + '@typescript-eslint/no-redundant-type-constituents': 'warn', + '@typescript-eslint/no-restricted-types': 'warn', + '@typescript-eslint/no-unsafe-enum-comparison': 'warn', + '@typescript-eslint/no-unsafe-declaration-merging': 'warn', + '@typescript-eslint/only-throw-error': 'warn', + '@typescript-eslint/no-require-imports': 'warn', + '@typescript-eslint/no-unsafe-call': 'warn', + '@typescript-eslint/no-unsafe-member-access': 'warn', + '@typescript-eslint/array-type': 'warn', + '@typescript-eslint/unbound-method': 'warn', + '@typescript-eslint/no-unsafe-assignment': 'warn', + 'no-useless-escape': 'warn', + '@typescript-eslint/prefer-optional-chain': 'warn', + '@typescript-eslint/no-duplicate-type-constituents': 'warn', + }, + }, + { + files: ['./src/databases/migrations/**/*.ts'], + rules: { + 'unicorn/filename-case': 'off', + }, + }, + { + files: [ + './src/databases/**/*.ts', + './src/modules/**/*.ts', + './test/**/*.ts', + './src/**/__tests__/**/*.ts', + ], + rules: { + 'n8n-local-rules/misplaced-n8n-typeorm-import': 'off', + }, + }, + { + files: ['./test/**/*.ts', './src/**/__tests__/**/*.ts'], + rules: { + 'n8n-local-rules/no-type-unsafe-event-emitter': 'off', + }, + }, + { + files: ['./src/decorators/**/*.ts'], + rules: { + '@typescript-eslint/no-restricted-types': [ + 'warn', + { + types: { + Function: false, + }, + }, + ], + }, + }, + { + files: ['./test/**/*.ts', './src/**/__tests__/**/*.ts'], + rules: { + 'id-denylist': 'warn', + 'prefer-const': 'warn', + 'n8n-local-rules/no-dynamic-import-template': 'off', + 'import-x/no-duplicates': 'warn', + 'import-x/no-default-export': 'warn', + '@typescript-eslint/no-unsafe-return': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + '@typescript-eslint/no-unused-expressions': 'warn', + '@typescript-eslint/restrict-template-expressions': 'warn', + 'n8n-local-rules/no-uncaught-json-parse': 'warn', + }, + }, +); diff --git a/packages/cli/src/commands/execute.ts b/packages/cli/src/commands/execute.ts index 59fe402a1a..910191d75b 100644 --- a/packages/cli/src/commands/execute.ts +++ b/packages/cli/src/commands/execute.ts @@ -109,7 +109,7 @@ export class Execute extends BaseCommand { this.logger.info(JSON.stringify(data, null, 2)); const { error } = data.data.resultData; - // eslint-disable-next-line @typescript-eslint/no-throw-literal + // eslint-disable-next-line @typescript-eslint/only-throw-error throw { ...error, stack: error.stack, diff --git a/packages/cli/src/config/index.ts b/packages/cli/src/config/index.ts index 031cb01e08..57d78d3cef 100644 --- a/packages/cli/src/config/index.ts +++ b/packages/cli/src/config/index.ts @@ -111,7 +111,7 @@ setGlobalState({ defaultTimezone: globalConfig.generic.timezone, }); -// eslint-disable-next-line import/no-default-export +// eslint-disable-next-line import-x/no-default-export export default config; export type Config = typeof config; diff --git a/packages/cli/src/credentials/credentials.service.ts b/packages/cli/src/credentials/credentials.service.ts index 3b2c02c4a9..5b8ea9fea3 100644 --- a/packages/cli/src/credentials/credentials.service.ts +++ b/packages/cli/src/credentials/credentials.service.ts @@ -38,7 +38,7 @@ import { userHasScopes } from '@/permissions.ee/check-access'; import type { CredentialRequest, ListQuery } from '@/requests'; import { CredentialsTester } from '@/services/credentials-tester.service'; import { OwnershipService } from '@/services/ownership.service'; -// eslint-disable-next-line import/no-cycle +// eslint-disable-next-line import-x/no-cycle import { ProjectService } from '@/services/project.service.ee'; import { RoleService } from '@/services/role.service'; diff --git a/packages/cli/src/execution-lifecycle/execute-error-workflow.ts b/packages/cli/src/execution-lifecycle/execute-error-workflow.ts index 753bf3febc..af7ab46bf1 100644 --- a/packages/cli/src/execution-lifecycle/execute-error-workflow.ts +++ b/packages/cli/src/execution-lifecycle/execute-error-workflow.ts @@ -7,7 +7,7 @@ import type { IRun, IWorkflowBase, WorkflowExecuteMode } from 'n8n-workflow'; import type { IWorkflowErrorData } from '@/interfaces'; import { OwnershipService } from '@/services/ownership.service'; import { UrlService } from '@/services/url.service'; -// eslint-disable-next-line import/no-cycle +// eslint-disable-next-line import-x/no-cycle import { WorkflowExecutionService } from '@/workflows/workflow-execution.service'; /** diff --git a/packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts b/packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts index cfff804965..6865e2a41b 100644 --- a/packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts +++ b/packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts @@ -17,7 +17,7 @@ import { WorkflowStatisticsService } from '@/services/workflow-statistics.servic import { isWorkflowIdValid } from '@/utils'; import { WorkflowStaticDataService } from '@/workflows/workflow-static-data.service'; -// eslint-disable-next-line import/no-cycle +// eslint-disable-next-line import-x/no-cycle import { executeErrorWorkflow } from './execute-error-workflow'; import { restoreBinaryDataId } from './restore-binary-data-id'; import { saveExecutionProgress } from './save-execution-progress'; diff --git a/packages/cli/src/help.ts b/packages/cli/src/help.ts index 67abc8ecde..2e531bf8e1 100644 --- a/packages/cli/src/help.ts +++ b/packages/cli/src/help.ts @@ -3,7 +3,7 @@ import { Container } from '@n8n/di'; import { Help } from '@oclif/core'; // oclif expects a default export -// eslint-disable-next-line import/no-default-export +// eslint-disable-next-line import-x/no-default-export export default class CustomHelp extends Help { async showRootHelp() { Container.get(Logger).info( diff --git a/packages/cli/src/load-nodes-and-credentials.ts b/packages/cli/src/load-nodes-and-credentials.ts index e9550fcdd6..59426bb424 100644 --- a/packages/cli/src/load-nodes-and-credentials.ts +++ b/packages/cli/src/load-nodes-and-credentials.ts @@ -516,7 +516,7 @@ export class LoadNodesAndCredentials { async setupHotReload() { const { default: debounce } = await import('lodash/debounce'); - // eslint-disable-next-line import/no-extraneous-dependencies + // eslint-disable-next-line import-x/no-extraneous-dependencies const { watch } = await import('chokidar'); const { Push } = await import('@/push'); diff --git a/packages/cli/src/services/cache/redis.cache-manager.ts b/packages/cli/src/services/cache/redis.cache-manager.ts index 6116b49757..f3a9e0b6b3 100644 --- a/packages/cli/src/services/cache/redis.cache-manager.ts +++ b/packages/cli/src/services/cache/redis.cache-manager.ts @@ -61,7 +61,7 @@ function builder( await redisCache.expire(key, ttlSeconds); }, async set(key, value, ttl) { - // eslint-disable-next-line @typescript-eslint/no-throw-literal, @typescript-eslint/restrict-template-expressions + // eslint-disable-next-line @typescript-eslint/only-throw-error, @typescript-eslint/restrict-template-expressions if (!isCacheable(value)) throw new NoCacheableError(`"${value}" is not a cacheable value`); const t = ttl ?? options?.ttl; if (t !== undefined && t !== 0) await redisCache.set(key, getVal(value), 'PX', t); @@ -73,7 +73,7 @@ function builder( const multi = redisCache.multi(); for (const [key, value] of args) { if (!isCacheable(value)) - // eslint-disable-next-line @typescript-eslint/no-throw-literal + // eslint-disable-next-line @typescript-eslint/only-throw-error throw new NoCacheableError(`"${getVal(value)}" is not a cacheable value`); multi.set(key, getVal(value), 'PX', t); } @@ -129,7 +129,7 @@ function builder( for (const field in fieldValueRecord) { const value = fieldValueRecord[field]; if (!isCacheable(fieldValueRecord[field])) { - // eslint-disable-next-line @typescript-eslint/no-throw-literal, @typescript-eslint/restrict-template-expressions + // eslint-disable-next-line @typescript-eslint/only-throw-error, @typescript-eslint/restrict-template-expressions throw new NoCacheableError(`"${value}" is not a cacheable value`); } fieldValueRecord[field] = getVal(value); diff --git a/packages/cli/src/services/folder.service.ts b/packages/cli/src/services/folder.service.ts index ec063fa092..4f2ece0d82 100644 --- a/packages/cli/src/services/folder.service.ts +++ b/packages/cli/src/services/folder.service.ts @@ -12,7 +12,7 @@ import { UserError, PROJECT_ROOT } from 'n8n-workflow'; import { FolderNotFoundError } from '@/errors/folder-not-found.error'; import type { ListQuery } from '@/requests'; -// eslint-disable-next-line import/no-cycle +// eslint-disable-next-line import-x/no-cycle import { WorkflowService } from '@/workflows/workflow.service'; export interface SimpleFolderNode { diff --git a/packages/cli/src/services/project.service.ee.ts b/packages/cli/src/services/project.service.ee.ts index 0053187711..8e019a2fcc 100644 --- a/packages/cli/src/services/project.service.ee.ts +++ b/packages/cli/src/services/project.service.ee.ts @@ -71,14 +71,14 @@ export class ProjectService { ) {} private get workflowService() { - // eslint-disable-next-line import/no-cycle + // eslint-disable-next-line import-x/no-cycle return import('@/workflows/workflow.service').then(({ WorkflowService }) => Container.get(WorkflowService), ); } private get credentialsService() { - // eslint-disable-next-line import/no-cycle + // eslint-disable-next-line import-x/no-cycle return import('@/credentials/credentials.service').then(({ CredentialsService }) => Container.get(CredentialsService), ); diff --git a/packages/cli/src/utlity.types.ts b/packages/cli/src/utlity.types.ts index c126a31717..4cf468792d 100644 --- a/packages/cli/src/utlity.types.ts +++ b/packages/cli/src/utlity.types.ts @@ -2,5 +2,5 @@ * Display an intersection type without implementation details. * @doc https://effectivetypescript.com/2022/02/25/gentips-4-display/ */ -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export type Resolve = T extends Function ? T : { [K in keyof T]: T[K] }; diff --git a/packages/cli/src/workflow-execute-additional-data.ts b/packages/cli/src/workflow-execute-additional-data.ts index d5f01ef4e3..d905aa2f28 100644 --- a/packages/cli/src/workflow-execute-additional-data.ts +++ b/packages/cli/src/workflow-execute-additional-data.ts @@ -36,7 +36,7 @@ import { ActiveExecutions } from '@/active-executions'; import { CredentialsHelper } from '@/credentials-helper'; import { EventService } from '@/events/event.service'; import type { AiEventMap, AiEventPayload } from '@/events/maps/ai.event-map'; -// eslint-disable-next-line import/no-cycle +// eslint-disable-next-line import-x/no-cycle import { getLifecycleHooksForSubExecutions } from '@/execution-lifecycle/execution-lifecycle-hooks'; import { ExecutionDataService } from '@/executions/execution-data.service'; import { diff --git a/packages/cli/src/workflow-runner.ts b/packages/cli/src/workflow-runner.ts index 60e2e08aba..9aaf49cdd0 100644 --- a/packages/cli/src/workflow-runner.ts +++ b/packages/cli/src/workflow-runner.ts @@ -23,7 +23,7 @@ import { ActiveExecutions } from '@/active-executions'; import config from '@/config'; import { ExecutionNotFoundError } from '@/errors/execution-not-found-error'; import { MaxStalledCountError } from '@/errors/max-stalled-count.error'; -// eslint-disable-next-line import/no-cycle +// eslint-disable-next-line import-x/no-cycle import { getLifecycleHooksForRegularMain, getLifecycleHooksForScalingWorker, diff --git a/packages/cli/src/workflows/workflow.service.ts b/packages/cli/src/workflows/workflow.service.ts index 1a8aead54f..73504dbe92 100644 --- a/packages/cli/src/workflows/workflow.service.ts +++ b/packages/cli/src/workflows/workflow.service.ts @@ -33,7 +33,7 @@ import { validateEntity } from '@/generic-helpers'; import type { ListQuery } from '@/requests'; import { hasSharing } from '@/requests'; import { OwnershipService } from '@/services/ownership.service'; -// eslint-disable-next-line import/no-cycle +// eslint-disable-next-line import-x/no-cycle import { ProjectService } from '@/services/project.service.ee'; import { RoleService } from '@/services/role.service'; import { TagService } from '@/services/tag.service'; diff --git a/packages/core/.eslintrc.js b/packages/core/.eslintrc.js deleted file mode 100644 index 849d69fe55..0000000000 --- a/packages/core/.eslintrc.js +++ /dev/null @@ -1,24 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/node'], - - ...sharedOptions(__dirname), - - parserOptions: { - project: './tsconfig.json', - }, - - ignorePatterns: ['bin/*.js', 'nodes-testing/*.ts'], - - rules: { - complexity: 'error', - 'unicorn/filename-case': ['error', { case: 'kebabCase' }], - - // TODO: Remove this - '@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': true }], - }, -}; diff --git a/packages/core/eslint.config.mjs b/packages/core/eslint.config.mjs new file mode 100644 index 0000000000..5729f09117 --- /dev/null +++ b/packages/core/eslint.config.mjs @@ -0,0 +1,46 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import { nodeConfig } from '@n8n/eslint-config/node'; + +export default defineConfig( + nodeConfig, + globalIgnores(['bin/*.js', 'nodes-testing/*.ts']), + { + rules: { + // TODO: Lower the complexity threshold + complexity: ['error', 27], + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + + // TODO: Remove these + 'no-prototype-builtins': 'warn', + 'no-empty': 'warn', + 'no-ex-assign': 'warn', + 'no-useless-escape': 'warn', + '@typescript-eslint/no-require-imports': 'warn', + '@typescript-eslint/require-await': 'warn', + '@typescript-eslint/no-base-to-string': 'warn', + '@typescript-eslint/prefer-optional-chain': 'warn', + '@typescript-eslint/prefer-nullish-coalescing': 'warn', + '@typescript-eslint/no-empty-object-type': 'warn', + '@typescript-eslint/naming-convention': 'warn', + '@typescript-eslint/no-array-delete': 'warn', + '@typescript-eslint/no-unsafe-member-access': 'warn', + }, + }, + { + files: ['**/*.test.ts', '**/test/**/*.ts', '**/__test__/**/*.ts', '**/__tests__/**/*.ts'], + rules: { + // TODO: Remove these + 'prefer-const': 'warn', + 'import-x/no-duplicates': 'warn', + 'import-x/no-default-export': 'warn', + 'n8n-local-rules/no-uncaught-json-parse': 'warn', + '@typescript-eslint/no-unsafe-assignment': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + '@typescript-eslint/no-unsafe-call': 'warn', + '@typescript-eslint/no-unsafe-return': 'warn', + '@typescript-eslint/unbound-method': 'warn', + '@typescript-eslint/no-unused-expressions': 'warn', + 'id-denylist': 'warn', + }, + }, +); diff --git a/packages/core/src/execution-engine/node-execution-context/supply-data-context.ts b/packages/core/src/execution-engine/node-execution-context/supply-data-context.ts index 9840bff171..500b60d890 100644 --- a/packages/core/src/execution-engine/node-execution-context/supply-data-context.ts +++ b/packages/core/src/execution-engine/node-execution-context/supply-data-context.ts @@ -31,7 +31,7 @@ import { constructExecutionMetaData } from './utils/construct-execution-metadata import { copyInputItems } from './utils/copy-input-items'; import { getDeduplicationHelperFunctions } from './utils/deduplication-helper-functions'; import { getFileSystemHelperFunctions } from './utils/file-system-helper-functions'; -// eslint-disable-next-line import/no-cycle +// eslint-disable-next-line import-x/no-cycle import { getInputConnectionData } from './utils/get-input-connection-data'; import { normalizeItems } from './utils/normalize-items'; import { getRequestHelperFunctions } from './utils/request-helper-functions'; diff --git a/packages/core/src/execution-engine/node-execution-context/utils/get-input-connection-data.ts b/packages/core/src/execution-engine/node-execution-context/utils/get-input-connection-data.ts index 7d0562bd2c..2222dcfbc1 100644 --- a/packages/core/src/execution-engine/node-execution-context/utils/get-input-connection-data.ts +++ b/packages/core/src/execution-engine/node-execution-context/utils/get-input-connection-data.ts @@ -29,7 +29,7 @@ import { import { createNodeAsTool } from './create-node-as-tool'; import type { ExecuteContext, WebhookContext } from '../../node-execution-context'; -// eslint-disable-next-line import/no-cycle +// eslint-disable-next-line import-x/no-cycle import { SupplyDataContext } from '../../node-execution-context/supply-data-context'; function getNextRunIndex(runExecutionData: IRunExecutionData, nodeName: string) { diff --git a/packages/extensions/insights/eslint.config.mjs b/packages/extensions/insights/eslint.config.mjs new file mode 100644 index 0000000000..0fc1aefa5e --- /dev/null +++ b/packages/extensions/insights/eslint.config.mjs @@ -0,0 +1,35 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import { baseConfig } from '@n8n/eslint-config/base'; + +export default defineConfig( + baseConfig, + globalIgnores(['src/shims.d.ts']), + { + rules: { + 'unicorn/filename-case': ['error', { case: 'kebabCase' }], + + // TODO: Remove these + 'import-x/order': 'warn', + 'import-x/no-default-export': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + '@typescript-eslint/no-unsafe-call': 'warn', + '@typescript-eslint/no-unsafe-member-access': 'warn', + }, + }, + { + files: ['src/backend/**/*.ts'], + languageOptions: { + parserOptions: { + project: ['./tsconfig.backend.json'], + }, + }, + }, + { + files: ['src/frontend/**/*.ts'], + languageOptions: { + parserOptions: { + project: ['./tsconfig.frontend.json'], + }, + }, + }, +); diff --git a/packages/extensions/insights/package.json b/packages/extensions/insights/package.json index d4a970dbe5..59bd63624a 100644 --- a/packages/extensions/insights/package.json +++ b/packages/extensions/insights/package.json @@ -30,6 +30,7 @@ "scripts": { "cleanup": "rimraf dist", "dev": "vite", + "lint": "eslint src --quiet", "typecheck": "vue-tsc --noEmit", "build:backend": "tsup", "build:frontend": "vite build", @@ -48,7 +49,7 @@ "@vitejs/plugin-vue": "catalog:frontend", "@vue/tsconfig": "catalog:frontend", "rimraf": "catalog:", - "vite": "catalog:frontend", + "vite": "catalog:", "vue": "catalog:frontend", "vue-router": "catalog:frontend", "vue-tsc": "catalog:frontend" diff --git a/packages/frontend/@n8n/chat/.eslintignore b/packages/frontend/@n8n/chat/.eslintignore deleted file mode 100644 index 40a7b4122b..0000000000 --- a/packages/frontend/@n8n/chat/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -.eslintrc.cjs -vitest.config.ts diff --git a/packages/frontend/@n8n/chat/.eslintrc.cjs b/packages/frontend/@n8n/chat/.eslintrc.cjs deleted file mode 100644 index 3f9a316c08..0000000000 --- a/packages/frontend/@n8n/chat/.eslintrc.cjs +++ /dev/null @@ -1,10 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/frontend'], - - ...sharedOptions(__dirname, 'frontend'), -}; diff --git a/packages/frontend/@n8n/chat/eslint.config.mjs b/packages/frontend/@n8n/chat/eslint.config.mjs new file mode 100644 index 0000000000..dea65e1c05 --- /dev/null +++ b/packages/frontend/@n8n/chat/eslint.config.mjs @@ -0,0 +1,17 @@ +import { frontendConfig } from '@n8n/eslint-config/frontend'; +import { defineConfig } from 'eslint/config'; + +export default defineConfig(frontendConfig, { + rules: { + // TODO: Remove these + 'no-empty': 'warn', + '@typescript-eslint/require-await': 'warn', + '@typescript-eslint/no-empty-object-type': 'warn', + '@typescript-eslint/naming-convention': 'warn', + '@typescript-eslint/no-unsafe-function-type': 'warn', + '@typescript-eslint/no-unsafe-call': 'warn', + '@typescript-eslint/no-unsafe-member-access': 'warn', + '@typescript-eslint/no-unsafe-return': 'warn', + '@typescript-eslint/no-unsafe-assignment': 'warn', + }, +}); diff --git a/packages/frontend/@n8n/chat/package.json b/packages/frontend/@n8n/chat/package.json index 2c7015b79e..447d88be97 100644 --- a/packages/frontend/@n8n/chat/package.json +++ b/packages/frontend/@n8n/chat/package.json @@ -10,8 +10,8 @@ "test:dev": "vitest", "test": "vitest run", "typecheck": "vue-tsc --noEmit", - "lint": "eslint . --ext .js,.ts,.vue --quiet", - "lintfix": "eslint . --ext .js,.ts,.vue --fix", + "lint": "eslint src --quiet", + "lintfix": "eslint src --fix", "format": "biome format --write src .storybook && prettier --write src/ --ignore-path ../../../../.prettierignore", "format:check": "biome ci src .storybook && prettier --check src/ --ignore-path ../../../../.prettierignore", "storybook": "storybook dev -p 6006 --no-open", @@ -51,10 +51,10 @@ "@n8n/typescript-config": "workspace:*", "@n8n/vitest-config": "workspace:*", "@vitejs/plugin-vue": "catalog:frontend", - "@vitest/coverage-v8": "catalog:frontend", + "@vitest/coverage-v8": "catalog:", "unplugin-icons": "^0.19.0", - "vite": "catalog:frontend", - "vitest": "catalog:frontend", + "vite": "catalog:", + "vitest": "catalog:", "vite-plugin-dts": "^4.5.3", "vue-tsc": "catalog:frontend" }, diff --git a/packages/frontend/@n8n/chat/src/__stories__/App.stories.ts b/packages/frontend/@n8n/chat/src/__stories__/App.stories.ts index 5c93917a74..3d08e3c625 100644 --- a/packages/frontend/@n8n/chat/src/__stories__/App.stories.ts +++ b/packages/frontend/@n8n/chat/src/__stories__/App.stories.ts @@ -25,7 +25,7 @@ const meta = { tags: ['autodocs'], }; -// eslint-disable-next-line import/no-default-export +// eslint-disable-next-line import-x/no-default-export export default meta; type Story = StoryObj; diff --git a/packages/frontend/@n8n/chat/src/__tests__/setup.ts b/packages/frontend/@n8n/chat/src/__tests__/setup.ts index 33e89fb68b..d7d7a324c0 100644 --- a/packages/frontend/@n8n/chat/src/__tests__/setup.ts +++ b/packages/frontend/@n8n/chat/src/__tests__/setup.ts @@ -1,5 +1,4 @@ import '@testing-library/jest-dom'; -import '@testing-library/jest-dom'; import { configure } from '@testing-library/vue'; configure({ testIdAttribute: 'data-test-id' }); diff --git a/packages/frontend/@n8n/chat/src/utils/event-bus.ts b/packages/frontend/@n8n/chat/src/utils/event-bus.ts index f6ffc597f5..f2835bd620 100644 --- a/packages/frontend/@n8n/chat/src/utils/event-bus.ts +++ b/packages/frontend/@n8n/chat/src/utils/event-bus.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-restricted-types export type CallbackFn = Function; export type UnregisterFn = () => void; diff --git a/packages/frontend/@n8n/composables/.eslintrc.cjs b/packages/frontend/@n8n/composables/.eslintrc.cjs deleted file mode 100644 index 3f9a316c08..0000000000 --- a/packages/frontend/@n8n/composables/.eslintrc.cjs +++ /dev/null @@ -1,10 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/frontend'], - - ...sharedOptions(__dirname, 'frontend'), -}; diff --git a/packages/frontend/@n8n/composables/eslint.config.mjs b/packages/frontend/@n8n/composables/eslint.config.mjs new file mode 100644 index 0000000000..1c110cea8c --- /dev/null +++ b/packages/frontend/@n8n/composables/eslint.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'eslint/config'; +import { frontendConfig } from '@n8n/eslint-config/frontend'; + +export default defineConfig(frontendConfig, { + files: ['**/*.test.ts'], + rules: { '@typescript-eslint/no-unsafe-assignment': 'warn' }, +}); diff --git a/packages/frontend/@n8n/composables/package.json b/packages/frontend/@n8n/composables/package.json index d257bcb086..223549a374 100644 --- a/packages/frontend/@n8n/composables/package.json +++ b/packages/frontend/@n8n/composables/package.json @@ -19,8 +19,8 @@ "typecheck": "vue-tsc --noEmit", "test": "vitest run", "test:dev": "vitest --silent=false", - "lint": "eslint src --ext .js,.ts,.vue --quiet", - "lintfix": "eslint src --ext .js,.ts,.vue --fix", + "lint": "eslint src --quiet", + "lintfix": "eslint src --fix", "format": "biome format --write . && prettier --write . --ignore-path ../../../../.prettierignore", "format:check": "biome ci . && prettier --check . --ignore-path ../../../../.prettierignore" }, @@ -36,9 +36,9 @@ "@vueuse/core": "catalog:frontend", "vue": "catalog:frontend", "tsup": "catalog:", - "typescript": "catalog:frontend", - "vite": "catalog:frontend", - "vitest": "catalog:frontend", + "typescript": "catalog:", + "vite": "catalog:", + "vitest": "catalog:", "vue-tsc": "catalog:frontend" }, "peerDependencies": { diff --git a/packages/frontend/@n8n/design-system/.eslintrc.js b/packages/frontend/@n8n/design-system/.eslintrc.js deleted file mode 100644 index 9a8cc924b8..0000000000 --- a/packages/frontend/@n8n/design-system/.eslintrc.js +++ /dev/null @@ -1,48 +0,0 @@ -const sharedOptions = require('@n8n/eslint-config/shared'); - -/** - * @type {import('@types/eslint').ESLint.ConfigData} - */ -module.exports = { - extends: ['@n8n/eslint-config/frontend'], - - ...sharedOptions(__dirname, 'frontend'), - - rules: { - // TODO: Remove these - 'import/no-default-export': 'warn', - '@typescript-eslint/no-unsafe-argument': 'warn', - '@typescript-eslint/no-unsafe-return': 'warn', - '@typescript-eslint/no-unsafe-member-access': 'warn', - '@typescript-eslint/prefer-optional-chain': 'warn', - '@typescript-eslint/prefer-nullish-coalescing': 'warn', - 'vue/no-undef-components': 'error', - }, - - overrides: [ - { - files: ['src/**/*.stories.ts', 'src/**/*.vue', 'src/**/*.spec.ts'], - rules: { - '@typescript-eslint/naming-convention': [ - 'warn', - { - selector: ['variable', 'property'], - format: ['PascalCase', 'camelCase', 'UPPER_CASE'], - }, - ], - }, - }, - { - files: ['src/components/N8nFormInput/validators.ts'], - rules: { - '@typescript-eslint/naming-convention': [ - 'error', - { - selector: ['property'], - format: ['camelCase', 'UPPER_CASE'], - }, - ], - }, - }, - ], -}; diff --git a/packages/frontend/@n8n/design-system/eslint.config.mjs b/packages/frontend/@n8n/design-system/eslint.config.mjs new file mode 100644 index 0000000000..14e2f8fab7 --- /dev/null +++ b/packages/frontend/@n8n/design-system/eslint.config.mjs @@ -0,0 +1,52 @@ +import { defineConfig } from 'eslint/config'; +import { frontendConfig } from '@n8n/eslint-config/frontend'; + +export default defineConfig( + frontendConfig, + { + rules: { + 'vue/no-undef-components': 'error', + + // TODO: Remove these + 'import-x/no-default-export': 'warn', + 'no-empty': 'warn', + 'no-prototype-builtins': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + '@typescript-eslint/no-unsafe-return': 'warn', + '@typescript-eslint/no-unsafe-member-access': 'warn', + '@typescript-eslint/prefer-optional-chain': 'warn', + '@typescript-eslint/prefer-nullish-coalescing': 'warn', + '@typescript-eslint/require-await': 'warn', + '@typescript-eslint/naming-convention': 'warn', + '@typescript-eslint/no-empty-object-type': 'warn', + '@typescript-eslint/no-unsafe-assignment': 'warn', + '@typescript-eslint/unbound-method': 'warn', + '@typescript-eslint/restrict-template-expressions': 'warn', + '@typescript-eslint/no-unsafe-call': 'warn', + }, + }, + { + files: ['src/**/*.stories.ts', 'src/**/*.vue', 'src/**/*.spec.ts'], + rules: { + '@typescript-eslint/naming-convention': [ + 'warn', + { + selector: ['variable', 'property'], + format: ['PascalCase', 'camelCase', 'UPPER_CASE'], + }, + ], + }, + }, + { + files: ['src/components/N8nFormInput/validators.ts'], + rules: { + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: ['property'], + format: ['camelCase', 'UPPER_CASE'], + }, + ], + }, + }, +); diff --git a/packages/frontend/@n8n/design-system/package.json b/packages/frontend/@n8n/design-system/package.json index 96f9bac636..5c5e9d4a4b 100644 --- a/packages/frontend/@n8n/design-system/package.json +++ b/packages/frontend/@n8n/design-system/package.json @@ -1,4 +1,5 @@ { + "type": "module", "name": "@n8n/design-system", "version": "1.87.0", "main": "src/index.ts", @@ -16,8 +17,8 @@ "chromatic": "chromatic", "format": "biome format --write . && prettier --write . --ignore-path ../../../../.prettierignore", "format:check": "biome ci . && prettier --check . --ignore-path ../../../../.prettierignore", - "lint": "eslint src --ext .js,.ts,.vue --quiet", - "lintfix": "eslint src --ext .js,.ts,.vue --fix" + "lint": "eslint src --quiet", + "lintfix": "eslint src --fix" }, "devDependencies": { "@n8n/eslint-config": "workspace:*", @@ -33,16 +34,16 @@ "@types/markdown-it-link-attributes": "^3.0.5", "@types/sanitize-html": "^2.11.0", "@vitejs/plugin-vue": "catalog:frontend", - "@vitest/coverage-v8": "catalog:frontend", + "@vitest/coverage-v8": "catalog:", "autoprefixer": "^10.4.19", "postcss": "^8.4.38", "sass": "^1.64.1", "tailwindcss": "^3.4.3", "unplugin-icons": "^0.19.0", "unplugin-vue-components": "^0.27.2", - "vite": "catalog:frontend", - "vitest": "catalog:frontend", - "vitest-mock-extended": "catalog:frontend", + "vite": "catalog:", + "vitest": "catalog:", + "vitest-mock-extended": "catalog:", "vue-tsc": "catalog:frontend" }, "dependencies": { diff --git a/packages/frontend/@n8n/design-system/postcss.config.js b/packages/frontend/@n8n/design-system/postcss.config.cjs similarity index 100% rename from packages/frontend/@n8n/design-system/postcss.config.js rename to packages/frontend/@n8n/design-system/postcss.config.cjs diff --git a/packages/frontend/@n8n/design-system/src/components/N8nIconPicker/IconPicker.vue b/packages/frontend/@n8n/design-system/src/components/N8nIconPicker/IconPicker.vue index c327bb15f9..1e89e51171 100644 --- a/packages/frontend/@n8n/design-system/src/components/N8nIconPicker/IconPicker.vue +++ b/packages/frontend/@n8n/design-system/src/components/N8nIconPicker/IconPicker.vue @@ -1,6 +1,6 @@