diff --git a/.github/workflows/studio-lint-ratchet.yml b/.github/workflows/studio-lint-ratchet.yml index bf34ef2b96c..9a3025584cb 100644 --- a/.github/workflows/studio-lint-ratchet.yml +++ b/.github/workflows/studio-lint-ratchet.yml @@ -44,8 +44,3 @@ jobs: - name: Run ratchet script run: pnpm --filter studio run lint:ratchet - - - name: Run type-aware ratchet script - env: - NODE_OPTIONS: --max-old-space-size=4096 - run: pnpm --filter studio run lint:ratchet:type-checks diff --git a/apps/studio/.github/eslint-rule-baselines.json b/apps/studio/.github/eslint-rule-baselines.json index 06b11a9be1a..e926e15baca 100644 --- a/apps/studio/.github/eslint-rule-baselines.json +++ b/apps/studio/.github/eslint-rule-baselines.json @@ -6,8 +6,7 @@ "@typescript-eslint/no-explicit-any": 990, "no-restricted-imports": 0, "no-restricted-exports": 251, - "react/no-unstable-nested-components": 51, - "studio/require-safe-sql-fragment": 158 + "react/no-unstable-nested-components": 51 }, "ruleFiles": { "react-hooks/exhaustive-deps": { @@ -930,148 +929,6 @@ "components/ui/ErrorBoundary/ErrorBoundary.tsx": 1, "components/ui/NoPermission.tsx": 1, "pages/project/[ref]/observability/database.tsx": 1 - }, - "studio/require-safe-sql-fragment": { - "components/interfaces/Auth/Hooks/CreateHookSheet.tsx": 1, - "components/interfaces/Auth/Hooks/HooksListing.tsx": 1, - "components/interfaces/Docs/Description.tsx": 1, - "components/interfaces/Integrations/CronJobs/CronJobsTab.useCleanupActions.ts": 2, - "components/interfaces/QueryPerformance/IndexAdvisor/index-advisor.utils.ts": 1, - "components/interfaces/QueryPerformance/WithStatements/WithStatements.tsx": 1, - "components/interfaces/QueryPerformance/useQueryPerformanceQuery.ts": 1, - "components/interfaces/Reports/ReportBlock/ReportBlock.tsx": 1, - "components/interfaces/Settings/API/DataApiEnableSwitch.utils.ts": 1, - "components/interfaces/Storage/PublicBucketWarning.tsx": 1, - "components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx": 12, - "components/ui/AIAssistantPanel/DisplayBlockRenderer.tsx": 1, - "components/ui/EditorPanel/EditorPanel.tsx": 1, - "data/api-settings/create-and-expose-api-schema-mutation.ts": 1, - "data/auth/index-worker-status-query.ts": 1, - "data/auth/user-query.ts": 1, - "data/auth/user-search-indexes-query.ts": 1, - "data/auth/users-count-query.ts": 1, - "data/auth/users-infinite-query.ts": 1, - "data/config/disk-breakdown-query.ts": 1, - "data/database-columns/database-column-create-mutation.ts": 1, - "data/database-columns/database-column-delete-mutation.ts": 1, - "data/database-columns/database-column-update-mutation.ts": 1, - "data/database-cron-jobs/database-cron-job-query.ts": 1, - "data/database-cron-jobs/database-cron-job-run-mutation.ts": 1, - "data/database-cron-jobs/database-cron-jobs-count-estimate-query.ts": 1, - "data/database-cron-jobs/database-cron-jobs-count-query.ts": 1, - "data/database-cron-jobs/database-cron-jobs-create-mutation.ts": 1, - "data/database-cron-jobs/database-cron-jobs-delete-mutation.ts": 1, - "data/database-cron-jobs/database-cron-jobs-infinite-query.ts": 1, - "data/database-cron-jobs/database-cron-jobs-minimal-infinite-query.ts": 1, - "data/database-cron-jobs/database-cron-jobs-runs-infinite-query.ts": 1, - "data/database-cron-jobs/database-cron-jobs-toggle-mutation.ts": 1, - "data/database-cron-jobs/database-cron-timezone-query.ts": 1, - "data/database-cron-jobs/schedule-clean-up-mutation.ts": 1, - "data/database-event-triggers/database-event-trigger-create-mutation.ts": 1, - "data/database-event-triggers/database-event-trigger-delete-mutation.ts": 1, - "data/database-event-triggers/database-event-triggers-query.ts": 1, - "data/database-extensions/database-extension-disable-mutation.ts": 1, - "data/database-extensions/database-extension-enable-mutation.ts": 1, - "data/database-extensions/database-extensions-query.ts": 1, - "data/database-functions/database-functions-create-mutation.ts": 1, - "data/database-functions/database-functions-delete-mutation.ts": 1, - "data/database-functions/database-functions-query.ts": 1, - "data/database-functions/database-functions-update-mutation.ts": 1, - "data/database-indexes/index-create-mutation.ts": 1, - "data/database-indexes/index-delete-mutation.ts": 1, - "data/database-indexes/indexes-query.ts": 1, - "data/database-integrations/stripe/sync-state-query.ts": 1, - "data/database-policies/database-policy-create-mutation.ts": 1, - "data/database-policies/database-policy-delete-mutation.ts": 1, - "data/database-policies/database-policy-update-mutation.ts": 1, - "data/database-publications/database-publications-create-mutation.ts": 1, - "data/database-publications/database-publications-update-mutation.ts": 1, - "data/database-queues/database-queue-messages-archive-mutation.ts": 1, - "data/database-queues/database-queue-messages-delete-mutation.ts": 1, - "data/database-queues/database-queue-messages-infinite-query.ts": 1, - "data/database-queues/database-queue-messages-read-mutation.ts": 1, - "data/database-queues/database-queue-messages-send-mutation.ts": 1, - "data/database-queues/database-queues-create-mutation.ts": 1, - "data/database-queues/database-queues-delete-mutation.ts": 1, - "data/database-queues/database-queues-expose-postgrest-status-query.ts": 1, - "data/database-queues/database-queues-metrics-query.ts": 2, - "data/database-queues/database-queues-purge-mutation.ts": 1, - "data/database-queues/database-queues-query.ts": 1, - "data/database-queues/database-queues-toggle-postgrest-mutation.ts": 1, - "data/database-roles/database-role-create-mutation.ts": 1, - "data/database-roles/database-role-delete-mutation.ts": 1, - "data/database-roles/database-role-update-mutation.ts": 1, - "data/database-roles/database-roles-query.ts": 1, - "data/database-triggers/database-trigger-create-mutation.ts": 1, - "data/database-triggers/database-trigger-delete-mutation.ts": 1, - "data/database-triggers/database-trigger-update-mutation.ts": 1, - "data/database-triggers/database-trigger-update-transaction-mutation.ts": 1, - "data/database/constraints-query.ts": 1, - "data/database/database-size-query.ts": 1, - "data/database/entity-definitions-query.ts": 1, - "data/database/foreign-key-constraints-query.ts": 1, - "data/database/keywords-query.ts": 1, - "data/database/max-connections-query.ts": 1, - "data/database/migrations-query.ts": 1, - "data/database/retrieve-index-advisor-result-query.ts": 1, - "data/database/retrieve-index-from-select-query.ts": 2, - "data/database/schema-create-mutation.ts": 1, - "data/database/schemas-query.ts": 1, - "data/database/supamonitor-enabled-query.ts": 1, - "data/database/table-columns-query.ts": 1, - "data/database/table-definition-query.ts": 1, - "data/database/table-index-advisor-query.ts": 1, - "data/database/view-definition-query.ts": 1, - "data/entity-types/entity-types-infinite-query.ts": 1, - "data/enumerated-types/enumerated-type-create-mutation.ts": 1, - "data/enumerated-types/enumerated-type-delete-mutation.ts": 1, - "data/enumerated-types/enumerated-type-update-mutation.ts": 1, - "data/fdw/fdw-create-mutation.ts": 1, - "data/fdw/fdw-delete-mutation.ts": 1, - "data/fdw/fdw-drop-foreign-table-mutation.ts": 1, - "data/fdw/fdw-import-foreign-schema-mutation.ts": 1, - "data/fdw/fdw-update-mutation.ts": 1, - "data/fdw/fdws-query.ts": 1, - "data/privileges/default-privileges-query.ts": 1, - "data/privileges/exposed-function-counts-query.ts": 1, - "data/privileges/exposed-functions-infinite-query.ts": 1, - "data/privileges/exposed-table-counts-query.ts": 1, - "data/privileges/exposed-tables-infinite-query.ts": 1, - "data/privileges/table-api-access-mutation.ts": 1, - "data/privileges/table-privileges-grant-mutation.ts": 1, - "data/privileges/table-privileges-query.ts": 1, - "data/privileges/table-privileges-revoke-mutation.ts": 1, - "data/privileges/update-default-privileges-mutation.ts": 1, - "data/privileges/update-exposed-entities-mutation.ts": 1, - "data/read-replicas/replica-lag-query.ts": 1, - "data/sql/abort-query-mutation.ts": 1, - "data/sql/execute-sql-query.ts": 1, - "data/sql/ongoing-queries-query.ts": 1, - "data/storage/bucket-prefix-delete-mutation.ts": 1, - "data/storage/buckets-max-size-limit-query.ts": 2, - "data/storage/public-buckets-with-select-policies-query.ts": 1, - "data/table-editor/table-editor-query.ts": 1, - "data/table-rows/get-cell-value-mutation.ts": 1, - "data/table-rows/operation-queue-save-mutation.ts": 1, - "data/table-rows/table-row-create-mutation.ts": 1, - "data/table-rows/table-row-delete-all-mutation.ts": 1, - "data/table-rows/table-row-delete-mutation.tsx": 1, - "data/table-rows/table-row-truncate-mutation.ts": 1, - "data/table-rows/table-row-update-mutation.ts": 1, - "data/table-rows/table-rows-count-query.ts": 1, - "data/table-rows/table-rows-query.ts": 3, - "data/tables/table-names-query.ts": 1, - "data/tables/tables-roles-access-query.ts": 1, - "data/vault/vault-secret-create-mutation.ts": 1, - "data/vault/vault-secret-decrypted-value-query.ts": 2, - "data/vault/vault-secret-delete-mutation.ts": 1, - "data/vault/vault-secret-update-mutation.ts": 1, - "data/vault/vault-secrets-query.ts": 1, - "hooks/analytics/useDbQuery.tsx": 1, - "lib/ai/tools/fallback-tools.ts": 1, - "pages/api/ai/code/complete.ts": 1, - "pages/api/ai/sql/generate-v4.ts": 1, - "state/role-impersonation-state.tsx": 1 } } } diff --git a/apps/studio/eslint-rules/require-safe-sql-fragment.cjs b/apps/studio/eslint-rules/require-safe-sql-fragment.cjs deleted file mode 100644 index 59318430f77..00000000000 --- a/apps/studio/eslint-rules/require-safe-sql-fragment.cjs +++ /dev/null @@ -1,70 +0,0 @@ -/** - * ESLint rule to require SafeSqlFragment (not a plain string) for the sql argument of executeSql. - * - * During migration, executeSql still accepts string (a supertype of SafeSqlFragment), but new - * call sites should use SafeSqlFragment to avoid SQL injection risks and to be ready for when - * the type is tightened. - * - * BAD: - * executeSql({ projectRef, sql: `SELECT * FROM ${table}` }) - * const query = buildQuery() // returns string - * executeSql({ projectRef, sql: query }) - * - * GOOD: - * executeSql({ projectRef, sql: safeSql`SELECT * FROM ${ident(table)}` }) - * executeSql({ projectRef, sql: keyword('SELECT 1') }) - */ - -const { ESLintUtils } = require('@typescript-eslint/utils') - -/** @type {import('@typescript-eslint/utils').TSESLint.RuleModule<'requireSafeSqlFragment'>} */ -module.exports = { - meta: { - type: 'problem', - docs: { - description: - 'Require executeSql to be called with SafeSqlFragment, not a plain string. Use safeSql`...`, ident(), literal(), or keyword() to create a SafeSqlFragment.', - recommended: true, - }, - messages: { - requireSafeSqlFragment: - 'The sql argument to executeSql must be SafeSqlFragment, not a plain string. Use safeSql`...`, ident(), literal(), or keyword() to construct it safely.', - }, - schema: [], - }, - - create(context) { - const services = ESLintUtils.getParserServices(context) - const checker = services.program.getTypeChecker() - - return { - CallExpression(node) { - const callee = node.callee - const isExecuteSql = - (callee.type === 'Identifier' && callee.name === 'executeSql') || - (callee.type === 'MemberExpression' && - callee.property.type === 'Identifier' && - callee.property.name === 'executeSql') - - if (!isExecuteSql) return - - const firstArg = node.arguments[0] - if (!firstArg || firstArg.type !== 'ObjectExpression') return - - const sqlProp = firstArg.properties.find( - (prop) => - prop.type === 'Property' && prop.key.type === 'Identifier' && prop.key.name === 'sql' - ) - if (!sqlProp || sqlProp.type !== 'Property') return - - const tsNode = services.esTreeNodeToTSNodeMap.get(sqlProp.value) - const type = checker.getTypeAtLocation(tsNode) - const hasBrand = checker.getPropertyOfType(type, '__safeSqlFragmentBrand') !== undefined - - if (!hasBrand) { - context.report({ node: sqlProp.value, messageId: 'requireSafeSqlFragment' }) - } - }, - } - }, -} diff --git a/apps/studio/eslint.type-checks.config.cjs b/apps/studio/eslint.type-checks.config.cjs deleted file mode 100644 index fe7a4570e2a..00000000000 --- a/apps/studio/eslint.type-checks.config.cjs +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Separate ESLint config for rules that require TypeScript type information. - * - * Run via: pnpm lint:type-checks - * - * Kept separate from eslint.config.cjs because loading a full TypeScript program - * (project: true) is memory-intensive and would slow down or OOM the main lint run. - */ - -const { defineConfig } = require('eslint/config') -const tsparser = require('@typescript-eslint/parser') -const requireSafeSqlFragment = require('./eslint-rules/require-safe-sql-fragment.cjs') - -const studioPlugin = { - rules: { - 'require-safe-sql-fragment': requireSafeSqlFragment, - }, -} - -module.exports = defineConfig([ - { - files: ['**/*.ts', '**/*.tsx'], - languageOptions: { - parser: tsparser, - parserOptions: { - project: true, - }, - }, - plugins: { - studio: studioPlugin, - }, - rules: { - 'studio/require-safe-sql-fragment': 'warn', - }, - }, -]) diff --git a/apps/studio/package.json b/apps/studio/package.json index 6a401404d68..080a7853ba1 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -11,7 +11,6 @@ "start": "next start -p 8082", "lint": "eslint .", "lint:ratchet": "tsx scripts/ratchet-eslint-rules.ts --rule react-hooks/exhaustive-deps --rule import/no-anonymous-default-export --rule @tanstack/query/exhaustive-deps --rule @typescript-eslint/no-explicit-any --rule no-restricted-imports --rule no-restricted-exports --rule react/no-unstable-nested-components", - "lint:ratchet:type-checks": "tsx scripts/ratchet-eslint-rules.ts --rule studio/require-safe-sql-fragment --eslint-args \"--config eslint.type-checks.config.cjs .\"", "clean": "rimraf node_modules tsconfig.tsbuildinfo .next .turbo", "test": "vitest --run --coverage", "test:watch": "vitest watch", @@ -176,7 +175,6 @@ "@types/recharts": "^1.8.23", "@types/sqlstring": "^2.3.0", "@types/zxcvbn": "^4.4.1", - "@typescript-eslint/utils": "8.48.0", "@vitejs/plugin-react": "catalog:", "@vitest/coverage-v8": "catalog:", "@vitest/ui": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2872ab35f5b..a7c2dacf181 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1284,9 +1284,6 @@ importers: '@types/zxcvbn': specifier: ^4.4.1 version: 4.4.2 - '@typescript-eslint/utils': - specifier: 8.48.0 - version: 8.48.0(eslint@9.37.0(jiti@2.6.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.2) '@vitejs/plugin-react': specifier: 'catalog:' version: 6.0.1(vite@8.0.8(@types/node@22.13.14)(esbuild@0.25.2)(jiti@2.6.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.3))