diff --git a/.claude/skills/_shared/typescript-commands.md b/.claude/skills/_shared/typescript-commands.md index d2372ac7dcb..5f3807e90b2 100644 --- a/.claude/skills/_shared/typescript-commands.md +++ b/.claude/skills/_shared/typescript-commands.md @@ -1,7 +1,7 @@ ## Linting and Formatting -- **Lint:** `bun run lint-eslint-pure` - - Run ESLint on the codebase +- **Lint:** `bun run lint-oxlint-pure` + - Run oxlint on the codebase - **Format:** `bun run format` - Format code with oxfmt (`oxfmt --write`); use `bun run lint-format-pure` to check without writing - **Type Check:** `bun run type-check-pure` diff --git a/.claude/skills/fe-modularization/SKILL.md b/.claude/skills/fe-modularization/SKILL.md index ba7fe46802e..f3b46b9be9c 100644 --- a/.claude/skills/fe-modularization/SKILL.md +++ b/.claude/skills/fe-modularization/SKILL.md @@ -39,7 +39,7 @@ what and who may depend on whom.** | module elements, tiers, `enforceSharedTiers`, `enforcePublicApi` | `frontend/lint/module-boundaries.mjs` | | shared sub-tiers and levels, cluster rules | `frontend/lint/shared-tiers.mjs` | | standalone boundaries lint (all violations, incl. grandfathered) | `bun run module-boundaries` (config `eslint.config.module-boundaries.mjs`) | -| PR lint (only enforced modules fail CI) | `bun run lint-eslint-pure` | +| PR lint (only enforced modules fail CI) | `bun run lint-oxlint-pure` | | side-effect-free directories and their exceptions | `frontend/build/shared/rspack/side-effect-free-modules.js` (`SIDE_EFFECT_FREE_PATHS`, `SIDE_EFFECT_PATHS`) | | the lint rules that enforce that promise | `metabase/no-module-side-effects`, `metabase/no-base-api-access` in `frontend/lint/eslint-plugin-metabase/rules/` | | public-api enforcement | `metabase/enforce-module-public-api` rule, driven by `getPublicApiModules()` | @@ -199,7 +199,7 @@ prefix; never generic `-shared` / `-feature` suffixes. ``` grep -rn "" frontend enterprise/frontend e2e # must be empty bunx eslint --no-warn-ignored - bun run lint-eslint-pure # enforce-module-public-api included + bun run lint-oxlint-pure # enforce-module-public-api included bun run type-check-pure bun run test-unit-keep-cljs bun run module-boundaries 2>&1 | tail -1 # after; no violation may name the new files diff --git a/.claude/skills/metabase-ui-component-from-figma/SKILL.md b/.claude/skills/metabase-ui-component-from-figma/SKILL.md index 16d2fbb52a3..97663ef6348 100644 --- a/.claude/skills/metabase-ui-component-from-figma/SKILL.md +++ b/.claude/skills/metabase-ui-component-from-figma/SKILL.md @@ -172,7 +172,7 @@ export const OverviewStory = { - **Scope controls per story.** A static matrix should hide knobs it ignores: `parameters.controls = { include: [...] }` (or `{ disable: true }`). Wire any kept knob (e.g. `children`) through to the cells so it does something. - `loki.config.js` has a `storiesFilter`; check it before renaming a story that's already covered. -Validate: `bun run lint-eslint-pure -- ` and `bun run type-check-pure`. +Validate: `bun run lint-oxlint-pure -- ` and `bun run type-check-pure`. ### Optional: verify in the browser yourself @@ -217,7 +217,7 @@ General gotchas when extending Mantine components — adapt per component: - **Built-in icons use `currentColor`**, so they inherit the slot's text color. - For text on a brand fill, follow the existing convention (`text-primary-inverse`) unless the user wants pure white; flag the dark-theme implication. -After edits: `npx stylelint `, `LINT_CSS_MODULES=true bun run lint-eslint-pure -- `, `bun run type-check-pure`. +After edits: `npx stylelint `, `LINT_CSS_MODULES=true bun run lint-oxlint-pure -- `, `bun run type-check-pure`. ## Phase 5 — Iterate with the user diff --git a/.github/file-paths.yaml b/.github/file-paths.yaml index 3925f62f890..e76cdcd49bf 100644 --- a/.github/file-paths.yaml +++ b/.github/file-paths.yaml @@ -39,6 +39,8 @@ frontend_configs: &frontend_configs - "package.json" - "babel.config.json" - "eslint.config.mjs" + - "oxlint.config.mts" + - ".oxfmtrc.json" - "postcss.config.js" - "rspack.config.js" - "rspack.main.config.js" @@ -75,6 +77,7 @@ frontend_all: &frontend_all - *frontend_ci - *frontend_sources - *frontend_specs + - "frontend/lint/**/!(*.md)" frontend_loki_ci: &frontend_loki_ci - ".github/workflows/loki.yml" diff --git a/.github/scripts/cache-keys.sh b/.github/scripts/cache-keys.sh index 7d3abb9d560..7027c803c40 100755 --- a/.github/scripts/cache-keys.sh +++ b/.github/scripts/cache-keys.sh @@ -78,10 +78,6 @@ CYPRESS_VERSION="$CYPRESS_VERSIONS" # and the key that identifies its cache entry cannot drift apart. CLOJURE_VERSION="1.12.0.1488" -# An incremental lint cache is only ever an accelerator, so it keys on the commit and always falls back -# to the newest entry on the prefix. The exact key never pre-exists, which keeps the entry current. -ESLINT_SHA="${GITHUB_SHA:-$(git rev-parse HEAD)}" - emit_multiline() { local name="$1"; shift printf '%s< { it("keys the Cypress binary on the resolved version, not the lockfile", () => { expect(get("cypress-key")).toMatch(/^cypress-Linux-\d+\.\d+\.\d+/); }); - - it("keys the ESLint cache on the commit", () => { - expect(get("eslint-key", { GITHUB_SHA: "deadbeef" })).toEqual( - "eslint-Linux-deadbeef", - ); - }); }); diff --git a/.github/scripts/file-paths.unit.spec.ts b/.github/scripts/file-paths.unit.spec.ts index 62db9f746ce..f082680182d 100644 --- a/.github/scripts/file-paths.unit.spec.ts +++ b/.github/scripts/file-paths.unit.spec.ts @@ -62,6 +62,32 @@ describe("file-paths.yaml", () => { expect(matches("ci_scripts", ".nvmrc")).toBe(true); }); + it.each([ + "frontend/lint/config.mjs", + "frontend/lint/oxlint/rule-map.json", + "frontend/lint/eslint-plugin-metabase/rules/no-module-side-effects.js", + "frontend/lint/tests/oxlint-config.test.mjs", + ])( + "runs frontend checks without forcing full suites when %s changes", + (file) => { + expect(matches("frontend_all", file)).toBe(true); + expect(matches("frontend_unit_infra", file)).toBe(false); + expect(matches("frontend_loki_infra", file)).toBe(false); + }, + ); + + it("treats frontend/lint/OXLINT.md as a documentation-only change", () => { + expect(matches("frontend_all", "frontend/lint/OXLINT.md")).toBe(false); + expect(matches("documentation", "frontend/lint/OXLINT.md")).toBe(true); + }); + + it.each([ + "frontend/lint/module-boundaries.mjs", + "frontend/lint/shared-tiers.mjs", + ])("keeps CI script coverage for %s", (file) => { + expect(matches("ci_scripts", file)).toBe(true); + }); + it.each([ "jest.config.js", "jest.base.conf.js", diff --git a/.github/workflows/cache-warm.yml b/.github/workflows/cache-warm.yml index 70fe924dba7..279a6789661 100644 --- a/.github/workflows/cache-warm.yml +++ b/.github/workflows/cache-warm.yml @@ -5,9 +5,6 @@ # build a 200MB archive and race for one reservation on every lockfile change, and all pull the Cypress # binary at once on a version bump. # -# The ESLint cache is the exception and saves from the lint job itself: it is under a megabyte and only a -# couple of jobs write it, so there is no herd to avoid. -# # A cache is readable from the ref that saved it, from that ref's pull requests, and - for every ref - # from the default branch. Warming on push to each merge target therefore serves the pull requests aimed # at it, and master's copy backs up every ref that has none of its own. diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 301036ae3b3..4f09a889a20 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -26,7 +26,7 @@ on: type: boolean default: false skip-lint: - description: "Skip linting jobs (oxfmt, eslint, css variables check)" + description: "Skip linting jobs (oxfmt, oxlint, css variables check)" type: boolean default: false skip-custom-viz: @@ -57,26 +57,16 @@ jobs: - name: Prepare back-end environment uses: ./.github/actions/prepare-backend - - name: Cache spec - id: cache-spec - run: .github/scripts/cache-keys.sh | tee -a "$GITHUB_OUTPUT" - # Small enough, and written by few enough jobs, that this one reads and writes its own cache - # rather than waiting on cache-warm. - - name: ESLint cache - uses: actions/cache@v5 - with: - path: ${{ steps.cache-spec.outputs.eslint-path }} - key: ${{ steps.cache-spec.outputs.eslint-key }} - restore-keys: ${{ steps.cache-spec.outputs.eslint-restore-key }} - - name: Compile CLJS run: bun run build-pure:cljs - name: Find CSS variables that are used but never defined run: bun run find-never-defined-css-variables - name: Run oxfmt formatting linter run: bun run lint-format-pure - - name: Run ESLint linter - run: bun run lint-eslint-pure + - name: Run oxlint linter (including module boundaries) + run: bun run lint-oxlint-pure + - name: Verify lint configuration and behavior + run: bun run test-oxlint - name: Run CSS linter run: bun run lint-css diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 3865a02eb4b..cdb3e84456c 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -1,4 +1,75 @@ { "printWidth": 80, - "trailingComma": "all" + "trailingComma": "all", + "sortImports": { + "customGroups": [ + { + "groupName": "internal-pre-metabase", + "elementNamePattern": [ + "__support__", + "__support__/**", + "assets", + "assets/**", + "build-configs", + "build-configs/**", + "cljs", + "cljs/**", + "docs", + "docs/**", + "e2e", + "e2e/**", + "ee-overrides", + "ee-overrides/**", + "ee-plugins", + "ee-plugins/**", + "embedding", + "embedding/**", + "embedding-sdk-bundle", + "embedding-sdk-bundle/**", + "embedding-sdk-package", + "embedding-sdk-package/**", + "embedding-sdk-shared", + "embedding-sdk-shared/**", + "img", + "img/**" + ] + }, + { + "groupName": "metabase-core", + "elementNamePattern": ["metabase", "metabase/**"] + }, + { + "groupName": "metabase-scoped", + "elementNamePattern": ["metabase-*/**", "metabase-*"] + }, + { + "groupName": "internal-post-metabase", + "elementNamePattern": [ + "sdk-ee-plugins", + "sdk-ee-plugins/**", + "sdk-iframe-embedding-ee-plugins", + "sdk-iframe-embedding-ee-plugins/**", + "sdk-iframe-embedding-script-ee-plugins", + "sdk-iframe-embedding-script-ee-plugins/**" + ] + } + ], + "groups": [ + ["value-builtin", "type-builtin"], + ["value-external", "type-external"], + "internal-pre-metabase", + { "newlinesBetween": false }, + "metabase-core", + { "newlinesBetween": false }, + "metabase-scoped", + { "newlinesBetween": false }, + "internal-post-metabase", + ["value-parent", "type-parent"], + ["value-sibling", "type-sibling"], + ["value-index", "type-index"] + ], + "newlinesBetween": true, + "order": "asc", + "ignoreCase": false + } } diff --git a/bin/embedding-sdk/fixup-types-after-compilation.js b/bin/embedding-sdk/fixup-types-after-compilation.js index 9b428614074..2c960500af1 100644 --- a/bin/embedding-sdk/fixup-types-after-compilation.js +++ b/bin/embedding-sdk/fixup-types-after-compilation.js @@ -1,6 +1,5 @@ #!/usr/bin/env node /* eslint-env node */ -/* eslint-disable import/order */ const fs = require("fs"); const path = require("path"); const { spawn } = require("child_process"); diff --git a/bun.lock b/bun.lock index 1bbd4edb387..e0caa228c73 100644 --- a/bun.lock +++ b/bun.lock @@ -115,7 +115,7 @@ "mustache": "^2.3.2", "normalizr": "^3.0.2", "ora": "^8.0.1", - "oxlint": "^1.58.0", + "oxlint": "1.85.0", "prettier": "^3.3.3", "process": "^0.11.10", "prosemirror-dropcursor": "1.8.2", @@ -241,6 +241,7 @@ "@types/redux-actions": "^2.6.5", "@types/semver": "^7.7.1", "@types/underscore": "^1.13.0", + "@typescript-eslint/eslint-plugin": "8.60.0", "@vitejs/plugin-react": "^6.0.2", "ajv": "^8.5.0", "babel-loader": "^9.1.2", @@ -272,13 +273,12 @@ "eslint-plugin-boundaries": "5.4.0", "eslint-plugin-chai-friendly": "^1.1.0", "eslint-plugin-cypress": "6.1.0", - "eslint-plugin-depend": "1.4.0", - "eslint-plugin-i18next": "6.1.3", - "eslint-plugin-import-x": "4.16.1", + "eslint-plugin-depend": "1.5.0", + "eslint-plugin-i18next": "6.1.4", + "eslint-plugin-import-x": "4.17.1", "eslint-plugin-jest": "29.12.1", "eslint-plugin-jest-dom": "^5.5.0", - "eslint-plugin-jest-formatting": "^3.1.0", - "eslint-plugin-no-only-tests": "^3.3.0", + "eslint-plugin-no-only-tests": "3.4.0", "eslint-plugin-react": "7.37.5", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-storybook": "9.1.17", @@ -290,6 +290,7 @@ "get-nonce": "^1.0.1", "glob": "^7.1.1", "globals": "^15.14.0", + "html-entities": "2.3.2", "html-webpack-plugin": "5.5.0", "husky": "^9.1.7", "inquirer-file-selector": "^0.2.1", @@ -321,6 +322,7 @@ "open-cli": "^8.0.0", "openapi-typescript": "^7.10.0", "otpauth": "^9.5.1", + "oxc-resolver": "11.24.2", "oxfmt": "^0.38.0", "patch-package": "^8.0.1", "pg": "^8.8.0", @@ -827,11 +829,11 @@ "@dual-bundle/import-meta-resolve": ["@dual-bundle/import-meta-resolve@4.2.1", "", {}, "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg=="], - "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + "@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="], - "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + "@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], "@emotion/babel-plugin": ["@emotion/babel-plugin@11.11.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", "@emotion/hash": "^0.9.1", "@emotion/memoize": "^0.8.1", "@emotion/serialize": "^1.1.2", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", "stylis": "4.2.0" } }, "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ=="], @@ -1167,7 +1169,7 @@ "@mswjs/interceptors": ["@mswjs/interceptors@0.37.6", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], "@nicolo-ribaudo/chokidar-2": ["@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3", "", {}, "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ=="], @@ -1217,6 +1219,44 @@ "@oxc-project/types": ["@oxc-project/types@0.139.0", "", {}, "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw=="], + "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA=="], + + "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg=="], + + "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w=="], + + "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q=="], + + "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw=="], + + "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg=="], + + "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA=="], + + "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA=="], + + "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw=="], + + "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA=="], + + "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw=="], + + "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w=="], + + "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ=="], + + "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg=="], + + "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw=="], + + "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg=="], + + "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.24.2", "", { "dependencies": { "@emnapi/core": "1.11.2", "@emnapi/runtime": "1.11.2", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q=="], + + "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg=="], + + "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw=="], + "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.38.0", "", { "os": "android", "cpu": "arm" }, "sha512-lTN4//sgYywK8ulQo7a/EZVzOTGomGQv2IG/7tMYdqTV3xN3QTqWpXcZBGUzaicC4B882N+5zJLYZ37IWfUMcg=="], "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.38.0", "", { "os": "android", "cpu": "arm64" }, "sha512-XbVgqR1WsIcCkfxwh2tdg3M1MWgR23YOboW2nbB8ab0gInNNLGy7cIAdr78XaoG/bGdaF4488XRhuGWq67xrzA=="], @@ -1255,43 +1295,43 @@ "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.38.0", "", { "os": "win32", "cpu": "x64" }, "sha512-wud1Hz0D2hYrhk6exxQQndn1htcA28wAcFb1vtP3ZXSzPFtMvc7ag/VNPv6nz6mDzM8X660jUwGEac99QcrVsA=="], - "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.58.0", "", { "os": "android", "cpu": "arm" }, "sha512-1T7UN3SsWWxpWyWGn1cT3ASNJOo+pI3eUkmEl7HgtowapcV8kslYpFQcYn431VuxghXakPNlbjRwhqmR37PFOg=="], + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.85.0", "", { "os": "android", "cpu": "arm" }, "sha512-q2KO/Zso9UT+OMn0NF9ywn4E4t0MI3yxiDhNyhsQ7DyQJrC4FhFE4TXOi4bktFnOWXTMds8qZSbpv2XwRaNOBg=="], - "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.58.0", "", { "os": "android", "cpu": "arm64" }, "sha512-GryzujxuiRv2YFF7bRy8mKcxlbuAN+euVUtGJt9KKbLT8JBUIosamVhcthLh+VEr6KE6cjeVMAQxKAzJcoN7dg=="], + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.85.0", "", { "os": "android", "cpu": "arm64" }, "sha512-SxLN3ALjoT9NNdvpjEevGeHvfzTAFrF0NBYB5tzK7/GtCKMze3j1e/m/X2ozqGj2U9hfGG/dg/OG8vpVK4PiDA=="], - "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.58.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7/bRSJIwl4GxeZL9rPZ11anNTyUO9epZrfEJH/ZMla3+/gbQ6xZixh9nOhsZ0QwsTW7/5J2A/fHbD1udC5DQQA=="], + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.85.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y/Sup/J4f0f9UGsSd/xyCNTeWL+gepO63GBdEDAfue9nBsnk9zMmnIXx1O6b1V8C90vB5nucYNZ0pbMXAp8zJA=="], - "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.58.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-EqdtJSiHweS2vfILNrpyJ6HUwpEq2g7+4Zx1FPi4hu3Hu7tC3znF6ufbXO8Ub2LD4mGgznjI7kSdku9NDD1Mkg=="], + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.85.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ApOSNC04ynpDTwvBD+//0wyfODRSbEzvRoKpX8teffmc27z8AockwSNeMXGJXn5KP85eahDgR/2llICWLkzcnw=="], - "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.58.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-VQt5TH4M42mY20F545G637RKxV/yjwVtKk2vfXuazfReSIiuvWBnv+FVSvIV5fKVTJNjt3GSJibh6JecbhGdBw=="], + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.85.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-bNrVrCOA/kHky3Tu79IXWXe5bhIgLXfUuUEDHlAGOHUk96MkvDZ1ecaQF19rwstrnaqfP1o9nBTqzIr9+ZHkUg=="], - "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.58.0", "", { "os": "linux", "cpu": "arm" }, "sha512-fBYcj4ucwpAtjJT3oeBdFBYKvNyjRSK+cyuvBOTQjh0jvKp4yeA4S/D0IsCHus/VPaNG5L48qQkh+Vjy3HL2/Q=="], + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.85.0", "", { "os": "linux", "cpu": "arm" }, "sha512-NUrzOJ1s/EqsVvfn2L/1D8Wro2LPIZUbihL8kOJLh5fEdGEN3rdOGUYq3HwnUIL8sjpoP+4N6RaGrgmMJnaMPw=="], - "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.58.0", "", { "os": "linux", "cpu": "arm" }, "sha512-0BeuFfwlUHlJ1xpEdSD1YO3vByEFGPg36uLjK1JgFaxFb4W6w17F8ET8sz5cheZ4+x5f2xzdnRrrWv83E3Yd8g=="], + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.85.0", "", { "os": "linux", "cpu": "arm" }, "sha512-UJXrAT3E/RWkEqXLIs2ehETja1qfgkPb+5gwLIIS+o/6cf+grHvoOXTa5997a/YNQfcJS0DRBTOfZt95cvOI1g=="], - "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.58.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-TXlZgnPTlxrQzxG9ZXU7BNwx1Ilrr17P3GwZY0If2EzrinqRH3zXPc3HrRcBJgcsoZNMuNL5YivtkJYgp467UQ=="], + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.85.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-lK40QLjI0HxigO7CjDDshEtfYIeiYS0020v5BHFPqN4uuQBQxd2K9LNom2dW15o9F1937quSCRVp4ZsVhdbYdg=="], - "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.58.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-zSoYRo5dxHLcUx93Stl2hW3hSNjPt99O70eRVWt5A1zwJ+FPjeCCANCD2a9R4JbHsdcl11TIQOjyigcRVOH2mw=="], + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.85.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-c2zbdBwGKreHXwRx3gWBuFGJxLhxgsg6YlZ+3H+RgRusU/UEV9jNwJ3HGYK+nRo0LvBa7mt6Kj86xoVotUo8cw=="], - "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.58.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NQ0U/lqxH2/VxBYeAIvMNUK1y0a1bJ3ZicqkF2c6wfakbEciP9jvIE4yNzCFpZaqeIeRYaV7AVGqEO1yrfVPjA=="], + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.85.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tlt/Hy8lZ97/lCPmCgw/B3k/mwh+BzaIPbPkldZEly7TwLmx0xe2CQcaW2g/rR0dOgS9JNGCZsMEqLhUNMGvaw=="], - "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.58.0", "", { "os": "linux", "cpu": "none" }, "sha512-X9J+kr3gIC9FT8GuZt0ekzpNUtkBVzMVU4KiKDSlocyQuEgi3gBbXYN8UkQiV77FTusLDPsovjo95YedHr+3yg=="], + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.85.0", "", { "os": "linux", "cpu": "none" }, "sha512-3tNR9Xey82X0zKuY1d8hJ6Rc9gwRDurmqGLnQZa5xqOXy8/YyiqFXjAtugkKLY82obOlpK1eSiDRlgcNPuxtIg=="], - "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.58.0", "", { "os": "linux", "cpu": "none" }, "sha512-CDze3pi1OO3Wvb/QsXjmLEY4XPKGM6kIo82ssNOgmcl1IdndF9VSGAE38YLhADWmOac7fjqhBw82LozuUVxD0Q=="], + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.85.0", "", { "os": "linux", "cpu": "none" }, "sha512-wbGRd5PqCcjkJFHhZuZ2OBSUQY9czlQsoA/cQQB9JK/L9mC5MQgGoKAh+xd8QjA5V+0D3j+Qd1lAWn1I8zlelA=="], - "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.58.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-b/89glbxFaEAcA6Uf1FvCNecBJEgcUTsV1quzrqXM/o4R1M4u+2KCVuyGCayN2UpsRWtGGLb+Ver0tBBpxaPog=="], + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.85.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-3Sn0kSrE4DPZCWV/8o+n4x3aFZxI9ulMnkYlwCbJ8eUVkwRK2IerohE/A/z3SNbCwoPFOCJmGE5Avrq0rrvdvQ=="], - "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.58.0", "", { "os": "linux", "cpu": "x64" }, "sha512-0/yYpkq9VJFCEcuRlrViGj8pJUFFvNS4EkEREaN7CB1EcLXJIaVSSa5eCihwBGXtOZxhnblWgxks9juRdNQI7w=="], + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.85.0", "", { "os": "linux", "cpu": "x64" }, "sha512-JY2pxxYfB62bAGfejljVCqc44etItehPuAyaeSAdMuEMtwNA00ggMnS66lC1oIhos6oOXUkuU6mZ9bpFh3BqWg=="], - "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.58.0", "", { "os": "linux", "cpu": "x64" }, "sha512-hr6FNvmcAXiH+JxSvaJ4SJ1HofkdqEElXICW9sm3/Rd5eC3t7kzvmLyRAB3NngKO2wzXRCAm4Z/mGWfrsS4X8w=="], + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.85.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5k74vZ6qJBjBHEOlBk9B/iv68Yu0F1Afw/vvT2ar6OGCqEeXLaSjXz2n/IPCbhLG22UoKoYEJTzpYraRdcp6PA=="], - "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.58.0", "", { "os": "none", "cpu": "arm64" }, "sha512-R+O368VXgRql1K6Xar+FEo7NEwfo13EibPMoTv3sesYQedRXd6m30Dh/7lZMxnrQVFfeo4EOfYIP4FpcgWQNHg=="], + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.85.0", "", { "os": "none", "cpu": "arm64" }, "sha512-GbAl5qt5TCkPLXTaIISZJnugrcBhra6rodcXc9jYt620UtdsTt71NlNmJmm0frxzFpd54x/G+MkitEJA8I/BoA=="], - "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.58.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q0FZiAY/3c4YRj4z3h9K1PgaByrifrfbBoODSeX7gy97UtB7pySPUQfC2B/GbxWU6k7CzQrRy5gME10PltLAFQ=="], + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.85.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-kjmws5MK0et2swk4ND85D7NVQyDHw162i6whtZDLUA/lo6FQyBZDcmMRCMcVZcNrAhIaftVb00x9ChGDOjjNJA=="], - "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.58.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-Y8FKBABrSPp9H0QkRLHDHOSUgM/309a3IvOVgPcVxYcX70wxJrk608CuTg7w+C6vEd724X5wJoNkBcGYfH7nNQ=="], + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.85.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-eSsIJx9n4yxvOqYTZyPEMyEXRmE60XH7xGAU7i0Qbsn1lf6Za3CWJ9aRd82oSFKXaxhp+sA6/yMJVRIpLpna6A=="], - "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.58.0", "", { "os": "win32", "cpu": "x64" }, "sha512-bCn5rbiz5My+Bj7M09sDcnqW0QJyINRVxdZ65x1/Y2tGrMwherwK/lpk+HRQCKvXa8pcaQdF5KY5j54VGZLwNg=="], + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.85.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pBebIPUpKKhWrhSMWhy8TdAZBewiXnfxmaAGxhzxM1068GagqFaTwgKlU6e+UyJ2sPR+VoHouhXuGJkQjsrDvA=="], "@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="], @@ -1695,7 +1735,7 @@ "@ts-morph/common": ["@ts-morph/common@0.26.0", "", { "dependencies": { "fast-glob": "^3.3.2", "minimatch": "^9.0.4", "path-browserify": "^1.0.1" } }, "sha512-/RmKAtctStXqM5nECMQ46duT74Hoig/DBzhWXGHcodlDNrgRbsbwwHqSKFNbca6z9Xt/CUWMeXOsC9QEN1+rqw=="], - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], "@types/argparse": ["@types/argparse@1.0.38", "", {}, "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA=="], @@ -1931,25 +1971,25 @@ "@types/yoga-layout": ["@types/yoga-layout@1.9.2", "", {}, "sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.54.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/type-utils": "8.54.0", "@typescript-eslint/utils": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.60.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/type-utils": "8.60.0", "@typescript-eslint/utils": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.60.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.60.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg=="], "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.60.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.60.0", "@typescript-eslint/types": "^8.60.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0" } }, "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0" } }, "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw=="], "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.60.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ=="], - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/utils": "8.54.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA=="], + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0", "@typescript-eslint/utils": "8.60.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.39.0", "", {}, "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="], "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.60.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.60.0", "@typescript-eslint/tsconfig-utils": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.54.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.60.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg=="], "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], @@ -2937,19 +2977,17 @@ "eslint-plugin-cypress": ["eslint-plugin-cypress@6.1.0", "", { "dependencies": { "globals": "^17.3.0" }, "peerDependencies": { "eslint": ">=9" } }, "sha512-B8sxtNpINDxFkmsu1qKYjg70VsP8SGneEXgLcZMk1bUZcW08S+JyaiMdof1x6dmt02FgOD7YkT4wOaOD5HotJw=="], - "eslint-plugin-depend": ["eslint-plugin-depend@1.4.0", "", { "dependencies": { "empathic": "^2.0.0", "module-replacements": "^2.10.1", "semver": "^7.6.3" } }, "sha512-MQs+m4nHSfgAO9bJDsBzqw0ofK/AOA0vfeY/6ahofqcUMLeM6/D1sTYs21fOhc17kNU/gn58YCtj20XaAssh2A=="], + "eslint-plugin-depend": ["eslint-plugin-depend@1.5.0", "", { "dependencies": { "empathic": "^2.0.0", "module-replacements": "^2.10.1", "semver": "^7.6.3" }, "peerDependencies": { "eslint": ">=8.40.0" } }, "sha512-i3UeLYmclf1Icp35+6W7CR4Bp2PIpDgBuf/mpmXK5UeLkZlvYJ21VuQKKHHAIBKRTPivPGX/gZl5JGno1o9Y0A=="], - "eslint-plugin-i18next": ["eslint-plugin-i18next@6.1.3", "", { "dependencies": { "lodash": "^4.17.21", "requireindex": "~1.1.0" } }, "sha512-z/h4oBRd9wI1ET60HqcLSU6XPeAh/EPOrBBTyCdkWeMoYrWAaUVA+DOQkWTiNIyCltG4NTmy62SQisVXxoXurw=="], + "eslint-plugin-i18next": ["eslint-plugin-i18next@6.1.4", "", { "dependencies": { "lodash": "^4.17.21", "requireindex": "~1.1.0" } }, "sha512-BekXQu1VaVFkREepQGsntBYoKuPsf89V16n/s2xpKMtsw0/lDseds1wBhj3RtO1dKnHsfTPaG+xul1vF0R7LEQ=="], - "eslint-plugin-import-x": ["eslint-plugin-import-x@4.16.1", "", { "dependencies": { "@typescript-eslint/types": "^8.35.0", "comment-parser": "^1.4.1", "debug": "^4.4.1", "eslint-import-context": "^0.1.9", "is-glob": "^4.0.3", "minimatch": "^9.0.3 || ^10.0.1", "semver": "^7.7.2", "stable-hash-x": "^0.2.0", "unrs-resolver": "^1.9.2" }, "peerDependencies": { "@typescript-eslint/utils": "^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "eslint-import-resolver-node": "*" }, "optionalPeers": ["@typescript-eslint/utils", "eslint-import-resolver-node"] }, "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ=="], + "eslint-plugin-import-x": ["eslint-plugin-import-x@4.17.1", "", { "dependencies": { "@typescript-eslint/types": "^8.56.0", "comment-parser": "^1.4.1", "debug": "^4.4.1", "eslint-import-context": "^0.1.9", "is-glob": "^4.0.3", "minimatch": "^9.0.3 || ^10.1.2", "semver": "^7.7.2", "stable-hash-x": "^0.2.0", "unrs-resolver": "^1.9.2" }, "peerDependencies": { "@typescript-eslint/utils": "^8.56.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "eslint-import-resolver-node": "*" }, "optionalPeers": ["@typescript-eslint/utils", "eslint-import-resolver-node"] }, "sha512-4cdstYkKCyjumM2Q9NSI03K8D2a9F4Ssz33K2lv2hQa4KmR9jPLwk3uWGtNvclfqBrPGfGuMBwsGMbe6dMRbfg=="], "eslint-plugin-jest": ["eslint-plugin-jest@29.12.1", "", { "dependencies": { "@typescript-eslint/utils": "^8.0.0" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "jest": "*" }, "optionalPeers": ["@typescript-eslint/eslint-plugin", "jest"] }, "sha512-Rxo7r4jSANMBkXLICJKS0gjacgyopfNAsoS0e3R9AHnjoKuQOaaPfmsDJPi8UWwygI099OV/K/JhpYRVkxD4AA=="], "eslint-plugin-jest-dom": ["eslint-plugin-jest-dom@5.5.0", "", { "dependencies": { "@babel/runtime": "^7.16.3", "requireindex": "^1.2.0" }, "peerDependencies": { "@testing-library/dom": "^8.0.0 || ^9.0.0 || ^10.0.0", "eslint": "^6.8.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" }, "optionalPeers": ["@testing-library/dom"] }, "sha512-CRlXfchTr7EgC3tDI7MGHY6QjdJU5Vv2RPaeeGtkXUHnKZf04kgzMPIJUXt4qKCvYWVVIEo9ut9Oq1vgXAykEA=="], - "eslint-plugin-jest-formatting": ["eslint-plugin-jest-formatting@3.1.0", "", {}, "sha512-XyysraZ1JSgGbLSDxjj5HzKKh0glgWf+7CkqxbTqb7zEhW7X2WHo5SBQ8cGhnszKN+2Lj3/oevBlHNbHezoc/A=="], - - "eslint-plugin-no-only-tests": ["eslint-plugin-no-only-tests@3.3.0", "", {}, "sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q=="], + "eslint-plugin-no-only-tests": ["eslint-plugin-no-only-tests@3.4.0", "", {}, "sha512-4S3/9Nb7A2tiMcpzEQE9bQSlpeOz6WJkgryBuou/SA8W2x2c8Zf4j0NvTKBjv6qNhF9T79tmkecm/0CHqV0UGg=="], "eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="], @@ -4129,9 +4167,11 @@ "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], + "oxc-resolver": ["oxc-resolver@11.24.2", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.24.2", "@oxc-resolver/binding-android-arm64": "11.24.2", "@oxc-resolver/binding-darwin-arm64": "11.24.2", "@oxc-resolver/binding-darwin-x64": "11.24.2", "@oxc-resolver/binding-freebsd-x64": "11.24.2", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.24.2", "@oxc-resolver/binding-linux-arm-musleabihf": "11.24.2", "@oxc-resolver/binding-linux-arm64-gnu": "11.24.2", "@oxc-resolver/binding-linux-arm64-musl": "11.24.2", "@oxc-resolver/binding-linux-ppc64-gnu": "11.24.2", "@oxc-resolver/binding-linux-riscv64-gnu": "11.24.2", "@oxc-resolver/binding-linux-riscv64-musl": "11.24.2", "@oxc-resolver/binding-linux-s390x-gnu": "11.24.2", "@oxc-resolver/binding-linux-x64-gnu": "11.24.2", "@oxc-resolver/binding-linux-x64-musl": "11.24.2", "@oxc-resolver/binding-openharmony-arm64": "11.24.2", "@oxc-resolver/binding-wasm32-wasi": "11.24.2", "@oxc-resolver/binding-win32-arm64-msvc": "11.24.2", "@oxc-resolver/binding-win32-x64-msvc": "11.24.2" } }, "sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw=="], + "oxfmt": ["oxfmt@0.38.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.38.0", "@oxfmt/binding-android-arm64": "0.38.0", "@oxfmt/binding-darwin-arm64": "0.38.0", "@oxfmt/binding-darwin-x64": "0.38.0", "@oxfmt/binding-freebsd-x64": "0.38.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.38.0", "@oxfmt/binding-linux-arm-musleabihf": "0.38.0", "@oxfmt/binding-linux-arm64-gnu": "0.38.0", "@oxfmt/binding-linux-arm64-musl": "0.38.0", "@oxfmt/binding-linux-ppc64-gnu": "0.38.0", "@oxfmt/binding-linux-riscv64-gnu": "0.38.0", "@oxfmt/binding-linux-riscv64-musl": "0.38.0", "@oxfmt/binding-linux-s390x-gnu": "0.38.0", "@oxfmt/binding-linux-x64-gnu": "0.38.0", "@oxfmt/binding-linux-x64-musl": "0.38.0", "@oxfmt/binding-openharmony-arm64": "0.38.0", "@oxfmt/binding-win32-arm64-msvc": "0.38.0", "@oxfmt/binding-win32-ia32-msvc": "0.38.0", "@oxfmt/binding-win32-x64-msvc": "0.38.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-RGYfnnxmCz8dMQ1Oo5KrYkNRc9cne2WL2vfE+datWNkgiSAkfUsqpGLR7rnkN6cQFgQkHDZH400eXN6izJ8Lww=="], - "oxlint": ["oxlint@1.58.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.58.0", "@oxlint/binding-android-arm64": "1.58.0", "@oxlint/binding-darwin-arm64": "1.58.0", "@oxlint/binding-darwin-x64": "1.58.0", "@oxlint/binding-freebsd-x64": "1.58.0", "@oxlint/binding-linux-arm-gnueabihf": "1.58.0", "@oxlint/binding-linux-arm-musleabihf": "1.58.0", "@oxlint/binding-linux-arm64-gnu": "1.58.0", "@oxlint/binding-linux-arm64-musl": "1.58.0", "@oxlint/binding-linux-ppc64-gnu": "1.58.0", "@oxlint/binding-linux-riscv64-gnu": "1.58.0", "@oxlint/binding-linux-riscv64-musl": "1.58.0", "@oxlint/binding-linux-s390x-gnu": "1.58.0", "@oxlint/binding-linux-x64-gnu": "1.58.0", "@oxlint/binding-linux-x64-musl": "1.58.0", "@oxlint/binding-openharmony-arm64": "1.58.0", "@oxlint/binding-win32-arm64-msvc": "1.58.0", "@oxlint/binding-win32-ia32-msvc": "1.58.0", "@oxlint/binding-win32-x64-msvc": "1.58.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.18.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-t4s9leczDMqlvOSjnbCQe7gtoLkWgBGZ7sBdCJ9EOj5IXFSG/X7OAzK4yuH4iW+4cAYe8kLFbC8tuYMwWZm+Cg=="], + "oxlint": ["oxlint@1.85.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.85.0", "@oxlint/binding-android-arm64": "1.85.0", "@oxlint/binding-darwin-arm64": "1.85.0", "@oxlint/binding-darwin-x64": "1.85.0", "@oxlint/binding-freebsd-x64": "1.85.0", "@oxlint/binding-linux-arm-gnueabihf": "1.85.0", "@oxlint/binding-linux-arm-musleabihf": "1.85.0", "@oxlint/binding-linux-arm64-gnu": "1.85.0", "@oxlint/binding-linux-arm64-musl": "1.85.0", "@oxlint/binding-linux-ppc64-gnu": "1.85.0", "@oxlint/binding-linux-riscv64-gnu": "1.85.0", "@oxlint/binding-linux-riscv64-musl": "1.85.0", "@oxlint/binding-linux-s390x-gnu": "1.85.0", "@oxlint/binding-linux-x64-gnu": "1.85.0", "@oxlint/binding-linux-x64-musl": "1.85.0", "@oxlint/binding-openharmony-arm64": "1.85.0", "@oxlint/binding-win32-arm64-msvc": "1.85.0", "@oxlint/binding-win32-ia32-msvc": "1.85.0", "@oxlint/binding-win32-x64-msvc": "1.85.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg=="], "p-limit": ["p-limit@7.3.0", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw=="], @@ -5069,7 +5109,7 @@ "ts-algebra": ["ts-algebra@1.2.2", "", {}, "sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA=="], - "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], "ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="], @@ -5579,12 +5619,16 @@ "@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], - "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], - "@rollup/pluginutils/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@rollup/pluginutils/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "@rspack/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@rspack/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@rspack/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + "@rushstack/node-core-library/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], "@rushstack/node-core-library/fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="], @@ -5667,7 +5711,7 @@ "@ts-morph/common/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - "@tybys/wasm-util/tslib": ["tslib@2.6.2", "", {}, "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="], + "@tybys/wasm-util/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@types/d3-contour/@types/geojson": ["@types/geojson@7946.0.8", "", {}, "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA=="], @@ -5687,37 +5731,15 @@ "@types/leaflet/@types/geojson": ["@types/geojson@7946.0.8", "", {}, "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA=="], - "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0" } }, "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw=="], + "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="], - "@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="], - - "@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg=="], - - "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="], - - "@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="], - - "@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="], - - "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.54.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.54.0", "@typescript-eslint/tsconfig-utils": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA=="], - - "@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="], - - "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg=="], + "@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.39.0", "", {}, "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - "@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], - - "@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="], - - "@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.54.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.54.0", "@typescript-eslint/tsconfig-utils": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA=="], - - "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="], - - "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.39.0", "", {}, "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg=="], "@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], @@ -6053,11 +6075,13 @@ "eslint-plugin-cypress/globals": ["globals@17.3.0", "", {}, "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw=="], - "eslint-plugin-depend/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + "eslint-plugin-depend/semver": ["semver@7.8.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg=="], - "eslint-plugin-import-x/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "eslint-plugin-import-x/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - "eslint-plugin-import-x/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + "eslint-plugin-import-x/semver": ["semver@7.8.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg=="], + + "eslint-plugin-jest/@typescript-eslint/utils": ["@typescript-eslint/utils@8.54.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA=="], "eslint-plugin-jest-dom/requireindex": ["requireindex@1.2.0", "", {}, "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww=="], @@ -6067,6 +6091,8 @@ "eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "eslint-plugin-storybook/@typescript-eslint/utils": ["@typescript-eslint/utils@8.54.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA=="], + "espree/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], @@ -6805,10 +6831,6 @@ "typedoc/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - "typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.60.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/type-utils": "8.60.0", "@typescript-eslint/utils": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.60.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw=="], - - "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.60.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA=="], - "unbox-primitive/which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], "unified/@types/unist": ["@types/unist@3.0.2", "", {}, "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ=="], @@ -7057,13 +7079,17 @@ "@redocly/cli/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], - "@rolldown/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], - "@rolldown/binding-wasm32-wasi/@emnapi/core/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@rolldown/binding-wasm32-wasi/@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], + "@rspack/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@rspack/binding-wasm32-wasi/@emnapi/core/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@rspack/binding-wasm32-wasi/@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@rspack/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@storybook/addon-docs/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], @@ -7103,32 +7129,16 @@ "@types/jest/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], - "@typescript-eslint/parser/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="], + "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="], - "@typescript-eslint/parser/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.39.0", "", {}, "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg=="], - - "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.54.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.54.0", "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g=="], - - "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.54.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw=="], - - "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.39.0", "", {}, "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg=="], - - "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.54.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.54.0", "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g=="], - - "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.54.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw=="], - - "@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], "@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="], "@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="], + "@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@visx/curve/@types/d3-shape/@types/d3-path": ["@types/d3-path@1.0.11", "", {}, "sha512-4pQMp8ldf7UaB/gR8Fvvy69psNHkTpD/pVw3vmEi8iZAB9EPMBruB1JvHO4BIq9QkUUd2lV1F5YXpMNj7JPBpw=="], "@visx/vendor/@types/d3-interpolate/@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], @@ -7303,8 +7313,6 @@ "detective-typescript/@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - "detective-typescript/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], - "es-get-iterator/get-intrinsic/has-proto": ["has-proto@1.0.1", "", {}, "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg=="], "es-get-iterator/get-intrinsic/hasown": ["hasown@2.0.1", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA=="], @@ -7321,10 +7329,20 @@ "eslint-import-resolver-webpack/resolve/is-core-module": ["is-core-module@2.13.1", "", { "dependencies": { "hasown": "^2.0.0" } }, "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw=="], - "eslint-plugin-import-x/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0" } }, "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg=="], + + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="], + + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.54.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.54.0", "@typescript-eslint/tsconfig-utils": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA=="], "eslint-plugin-react/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "eslint-plugin-storybook/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0" } }, "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg=="], + + "eslint-plugin-storybook/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="], + + "eslint-plugin-storybook/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.54.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.54.0", "@typescript-eslint/tsconfig-utils": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA=="], + "eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "eslint/ajv/uri-js": ["uri-js@4.4.0", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g=="], @@ -7811,18 +7829,6 @@ "typed-array-length/is-typed-array/which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="], - "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0" } }, "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw=="], - - "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0", "@typescript-eslint/utils": "8.60.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg=="], - - "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg=="], - - "typescript-eslint/@typescript-eslint/eslint-plugin/ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], - - "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0" } }, "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw=="], - - "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="], - "unbox-primitive/which-boxed-primitive/is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="], "unbox-primitive/which-boxed-primitive/is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="], @@ -7973,7 +7979,7 @@ "@redocly/cli/glob/minimatch/brace-expansion": ["brace-expansion@5.0.2", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw=="], - "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@rspack/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util/tslib": ["tslib@2.6.2", "", {}, "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="], "@storybook/addon-webpack5-compiler-babel/babel-loader/find-cache-dir/pkg-dir": ["pkg-dir@7.0.0", "", { "dependencies": { "find-up": "^6.3.0" } }, "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA=="], @@ -7995,20 +8001,14 @@ "@ts-morph/common/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "@typescript-eslint/parser/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="], - - "@typescript-eslint/parser/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - - "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - - "@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - "@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], "@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/tslib": ["tslib@2.6.2", "", {}, "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="], "@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime/tslib": ["tslib@2.6.2", "", {}, "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="], + "@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util/tslib": ["tslib@2.6.2", "", {}, "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="], + "@vue/compiler-core/@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], "@vue/compiler-sfc/@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], @@ -8089,7 +8089,33 @@ "eslint-import-resolver-webpack/resolve/is-core-module/hasown": ["hasown@2.0.1", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA=="], - "eslint-plugin-import-x/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="], + + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.54.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.54.0", "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g=="], + + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.54.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw=="], + + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="], + + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], + + "eslint-plugin-storybook/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="], + + "eslint-plugin-storybook/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.54.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.54.0", "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g=="], + + "eslint-plugin-storybook/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.54.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw=="], + + "eslint-plugin-storybook/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="], + + "eslint-plugin-storybook/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "eslint-plugin-storybook/@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "eslint-plugin-storybook/@typescript-eslint/utils/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], "eslint/ajv/uri-js/punycode": ["punycode@2.1.1", "", {}, "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="], @@ -8397,10 +8423,6 @@ "test-exclude/glob/path-scurry/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="], - "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="], - - "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="], - "webpack-dev-middleware/schema-utils/ajv-formats/ajv": ["ajv@8.10.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw=="], "webpack/browserslist/update-browserslist-db/escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -8497,10 +8519,6 @@ "@storybook/builder-webpack5/terser-webpack-plugin/schema-utils/ajv-formats/ajv": ["ajv@8.10.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw=="], - "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "assert/object.assign/call-bind/get-intrinsic/has-proto": ["has-proto@1.0.1", "", {}, "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg=="], "assert/object.assign/call-bind/get-intrinsic/hasown": ["hasown@2.0.1", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA=="], @@ -8519,6 +8537,18 @@ "es-get-iterator/stop-iteration-iterator/internal-slot/side-channel/object-inspect": ["object-inspect@1.13.0", "", {}, "sha512-HQ4J+ic8hKrgIt3mqk6cVOVrW2ozL4KdvHlqpBv9vDYWx9ysAgENAdvy4FoGF+KFdhR7nQTNm5J0ctAeOwn+3g=="], + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + + "eslint-plugin-storybook/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "eslint-plugin-storybook/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "eslint-plugin-storybook/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + "fork-ts-checker-webpack-plugin/schema-utils/ajv/uri-js/punycode": ["punycode@2.1.1", "", {}, "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="], "is-typed-array/es-abstract/function.prototype.name/define-properties/has-property-descriptors": ["has-property-descriptors@1.0.0", "", { "dependencies": { "get-intrinsic": "^1.1.1" } }, "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ=="], @@ -8679,10 +8709,6 @@ "test-exclude/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.2", "", { "dependencies": { "jackspeak": "^4.2.3" } }, "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg=="], - "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="], - - "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - "webpack-dev-middleware/schema-utils/ajv-formats/ajv/uri-js": ["uri-js@4.4.0", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g=="], "webpack/schema-utils/ajv-formats/ajv/uri-js": ["uri-js@4.4.0", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g=="], @@ -8731,6 +8757,10 @@ "compression-rspack-plugin/schema-utils/ajv-formats/ajv/uri-js/punycode": ["punycode@2.1.1", "", {}, "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="], + "eslint-plugin-jest/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "eslint-plugin-storybook/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "jest-config/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "jest-config/glob/jackspeak/@isaacs/cliui/string-width/strip-ansi": ["strip-ansi@7.0.1", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw=="], diff --git a/docs/developers-guide/frontend.md b/docs/developers-guide/frontend.md index b81595cb4ea..7ddd84b4d5c 100644 --- a/docs/developers-guide/frontend.md +++ b/docs/developers-guide/frontend.md @@ -174,11 +174,11 @@ const output = c("{0} and {2} are people's names, and {1} is a place") The first rule of frontend style, is we want to avoid talking about frontend style. Wherever possible, style-level considerations should be encapsulated in lint rules. -### oxfmt + ESlint +### oxfmt + oxlint -We use [oxfmt](https://oxc.rs/) to format our JavaScript and TypeScript code, and it is enforced by CI. We recommend setting your editor to "format on save". You can also format code using `bun run format`, and verify it has been formatted correctly using `bun run lint-format-pure`. +We use [oxfmt](https://oxc.rs/) to format our JavaScript and TypeScript code, and it is enforced by CI. We recommend setting your editor to "format on save". You can also format code using `bun run format`, and verify it has been formatted correctly using `bun run lint-format-pure`. Oxfmt also sorts imports, so `bun run format` fixes import order too. -We use ESLint to enforce additional rules. It is integrated into the Webpack build, or you can manually run `bun run lint-eslint` to check. Nitpicky things like import order, spacing, etc. are all enforced by eslint. +We use [oxlint](https://oxc.rs/) to enforce JavaScript and TypeScript lint rules, including module boundaries. Run `bun run lint-oxlint` to lint, or `bun run lint-oxlint-fix` to apply lint fixes. Both commands build the CLJS output first. If you've already built it, `bun run lint-oxlint-pure` skips that step. For how the lint configuration works, see `frontend/lint/OXLINT.md`. ### Miscellaneous notes on coding style diff --git a/e2e/support/assets/data-apps/isolation/src/App.tsx b/e2e/support/assets/data-apps/isolation/src/App.tsx index 9512ac66eea..8bca01fc91e 100644 --- a/e2e/support/assets/data-apps/isolation/src/App.tsx +++ b/e2e/support/assets/data-apps/isolation/src/App.tsx @@ -1,11 +1,11 @@ import { useRef, useState } from "react"; -import { createProbes } from "./probes"; import { REACT_PROBES, ReactProbeHost, useProbeCustomElement, } from "./ReactProbes"; +import { createProbes } from "./probes"; import type { ReactMode } from "./types"; import { describeError, getEnv } from "./utils"; diff --git a/e2e/support/commands.js b/e2e/support/commands.js index 2b503e6dc49..2a3e9c9d8dc 100644 --- a/e2e/support/commands.js +++ b/e2e/support/commands.js @@ -1,5 +1,4 @@ import "cypress-iframe"; - // this is the only place we allow direct helper import // eslint-disable-next-line metabase/no-direct-helper-import import { H } from "e2e/support"; @@ -7,25 +6,17 @@ import { H } from "e2e/support"; import "./commands/ui/button"; import "./commands/ui/icon"; import "./commands/ui/paste"; - import "./commands/user/createUser"; import "./commands/user/authentication"; - import "./commands/permissions/updatePermissions"; import "./commands/permissions/sandboxTable"; - import "./commands/database/addSQLiteDatabase"; - import "./commands/visibility/isVisibleInPopover"; import "./commands/visibility/findByTextEnsureVisible"; import "./commands/visibility/isRenderedWithinViewport"; - import "./commands/overwrites/log"; - import "./commands/skip-test"; - import "./commands/component"; - import { addCustomCommands } from "./commands/downloads/downloadUtils"; addCustomCommands(); diff --git a/e2e/test-component/scenarios/embedding-sdk/mantine-styles-tests.cy.spec.tsx b/e2e/test-component/scenarios/embedding-sdk/mantine-styles-tests.cy.spec.tsx index 698ed09edbd..ea80d5cddce 100644 --- a/e2e/test-component/scenarios/embedding-sdk/mantine-styles-tests.cy.spec.tsx +++ b/e2e/test-component/scenarios/embedding-sdk/mantine-styles-tests.cy.spec.tsx @@ -1,11 +1,9 @@ // To apply cypress-level mantine styles import "@mantine/core/styles.css"; import "@mantine/dates/styles.css"; - // Mimics behavior when an app adds its own styles // We need to do it before importing the SDK import "e2e/support/helpers/embedding-sdk-helpers/host-app-styles.css"; - import { Button, MantineProvider, Title } from "@mantine/core"; import { InteractiveQuestion, diff --git a/enterprise/frontend/src/custom-viz/fixtures/build-example-custom-viz.mjs b/enterprise/frontend/src/custom-viz/fixtures/build-example-custom-viz.mjs index d0093f17d18..89809171ea3 100644 --- a/enterprise/frontend/src/custom-viz/fixtures/build-example-custom-viz.mjs +++ b/enterprise/frontend/src/custom-viz/fixtures/build-example-custom-viz.mjs @@ -6,7 +6,6 @@ * bun run build:custom-viz-fixtures */ -/* global console */ import { execFileSync } from "node:child_process"; import { cpSync, mkdirSync, rmSync, statSync } from "node:fs"; import { dirname, resolve } from "node:path"; @@ -37,7 +36,10 @@ const visualizations = [ }, { index: resolve(PLUGIN_ROOT, "src/index-widget-security-component.tsx"), - manifest: resolve(PLUGIN_ROOT, "manifests/demo-viz-security-component.json"), + manifest: resolve( + PLUGIN_ROOT, + "manifests/demo-viz-security-component.json", + ), out: resolve( E2E_ASSETS, "example_custom_viz_plugin_4_security_component.tgz", diff --git a/enterprise/frontend/src/custom-viz/fixtures/example_custom_viz_plugin/src/Visualization.tsx b/enterprise/frontend/src/custom-viz/fixtures/example_custom_viz_plugin/src/Visualization.tsx index a4b548e27e2..2af86b81ab9 100644 --- a/enterprise/frontend/src/custom-viz/fixtures/example_custom_viz_plugin/src/Visualization.tsx +++ b/enterprise/frontend/src/custom-viz/fixtures/example_custom_viz_plugin/src/Visualization.tsx @@ -1,5 +1,6 @@ import type { MouseEvent } from "react"; import { useState } from "react"; + import { type CustomVisualizationProps, formatValue } from "../../../src/index"; type Settings = { diff --git a/enterprise/frontend/src/custom-viz/fixtures/example_custom_viz_plugin/src/index-widget-security.tsx b/enterprise/frontend/src/custom-viz/fixtures/example_custom_viz_plugin/src/index-widget-security.tsx index 44654b88993..81919b0ebd1 100644 --- a/enterprise/frontend/src/custom-viz/fixtures/example_custom_viz_plugin/src/index-widget-security.tsx +++ b/enterprise/frontend/src/custom-viz/fixtures/example_custom_viz_plugin/src/index-widget-security.tsx @@ -1,5 +1,6 @@ import { defineConfig } from "../../../src/index"; import type { CreateCustomVisualization } from "../../../src/types/viz"; + import { Visualization } from "./Visualization"; type Settings = { diff --git a/enterprise/frontend/src/custom-viz/fixtures/example_custom_viz_plugin/src/index.tsx b/enterprise/frontend/src/custom-viz/fixtures/example_custom_viz_plugin/src/index.tsx index 292be5f3104..d5a63904937 100644 --- a/enterprise/frontend/src/custom-viz/fixtures/example_custom_viz_plugin/src/index.tsx +++ b/enterprise/frontend/src/custom-viz/fixtures/example_custom_viz_plugin/src/index.tsx @@ -3,6 +3,7 @@ import type { BaseWidgetProps, CreateCustomVisualization, } from "../../../src/types/viz"; + import { Visualization } from "./Visualization"; type Settings = { diff --git a/enterprise/frontend/src/embedding-sdk-ee/auth/auth.ts b/enterprise/frontend/src/embedding-sdk-ee/auth/auth.ts index 58e14f92f0c..2b029972754 100644 --- a/enterprise/frontend/src/embedding-sdk-ee/auth/auth.ts +++ b/enterprise/frontend/src/embedding-sdk-ee/auth/auth.ts @@ -3,12 +3,6 @@ import type { AsyncThunkAction } from "@reduxjs/toolkit"; import { EMBEDDING_SDK_PACKAGE_UNKNOWN_VERSION } from "build-configs/embedding-sdk/constants/versions"; -import { - connectToInstanceAuthSso, - jwtDefaultRefreshTokenFunction, - openSamlLoginPopup, - validateSession, -} from "embedding/auth-common"; import { getIsLocalhost } from "embedding-sdk-bundle/lib/get-is-localhost"; import { PLUGIN_EMBEDDING_SDK_AUTH, @@ -28,15 +22,18 @@ import { getWindow } from "embedding-sdk-shared/lib/get-window"; import type { MetabaseAuthConfig } from "embedding-sdk-shared/types/auth-config"; import type { SdkAuthState } from "embedding-sdk-shared/types/auth-state"; import { SDK_AUTH_STATE_KEY } from "embedding-sdk-shared/types/auth-state"; +import { + connectToInstanceAuthSso, + jwtDefaultRefreshTokenFunction, + openSamlLoginPopup, + validateSession, +} from "embedding/auth-common"; import { PLUGIN_API } from "metabase/api/client"; import { currentUserApi, loadCurrentUser, refetchCurrentUser, } from "metabase/current-user"; -import { requestSessionTokenFromEmbedJs } from "metabase/embedding/embedding-iframe-sdk/utils"; -import { getSessionTokenHeaders } from "metabase/embedding/lib/auth/get-session-token-headers"; -import { setApiKeyHeader } from "metabase/embedding/lib/auth/set-api-key-header"; import { EMBEDDING_SDK_IFRAME_EMBEDDING_CONFIG, isEmbeddingEajs, @@ -44,6 +41,9 @@ import { } from "metabase/embedding-sdk/config"; import { samlTokenStorage } from "metabase/embedding-sdk/lib/saml-token-storage"; import type { MetabaseEmbeddingSessionToken } from "metabase/embedding-sdk/types/refresh-token"; +import { requestSessionTokenFromEmbedJs } from "metabase/embedding/embedding-iframe-sdk/utils"; +import { getSessionTokenHeaders } from "metabase/embedding/lib/auth/get-session-token-headers"; +import { setApiKeyHeader } from "metabase/embedding/lib/auth/set-api-key-header"; import { createAsyncThunk } from "metabase/redux/utils"; import { refetchSiteSettings, settingsApi } from "metabase/settings"; import MetabaseSettings from "metabase/utils/settings"; diff --git a/enterprise/frontend/src/embedding-sdk-ee/metabot/index.ts b/enterprise/frontend/src/embedding-sdk-ee/metabot/index.ts index 5ea7e8615de..6a115012088 100644 --- a/enterprise/frontend/src/embedding-sdk-ee/metabot/index.ts +++ b/enterprise/frontend/src/embedding-sdk-ee/metabot/index.ts @@ -1,7 +1,6 @@ // Contains side effects that puts `MetabotQuestion` and `useMetabot` in the plugin import "./MetabotQuestion"; import "./hooks/use-metabot"; - import { METABOT_SDK_EE_PLUGIN } from "embedding-sdk-bundle/components/public/MetabotQuestion/MetabotQuestion"; import { MetabotProvider } from "metabase/metabot/context"; diff --git a/enterprise/frontend/src/embedding-sdk-package/components/private/Loader/Loader.tsx b/enterprise/frontend/src/embedding-sdk-package/components/private/Loader/Loader.tsx index 49f92b81a07..0151a47e421 100644 --- a/enterprise/frontend/src/embedding-sdk-package/components/private/Loader/Loader.tsx +++ b/enterprise/frontend/src/embedding-sdk-package/components/private/Loader/Loader.tsx @@ -1,7 +1,7 @@ -// eslint-disable-next-line metabase/no-external-references-for-sdk-package-code -import { getSdkLoaderCss } from "embedding/sdk-common/lib/get-sdk-loader-css"; import type { CommonStylingProps } from "embedding-sdk-bundle/types/props"; import { useMetabaseProviderPropsStore } from "embedding-sdk-package/lib/provider-props-store"; +// eslint-disable-next-line metabase/no-external-references-for-sdk-package-code +import { getSdkLoaderCss } from "embedding/sdk-common/lib/get-sdk-loader-css"; import type { MetabaseTheme } from "metabase/embedding-sdk/theme"; type SpinnerProps = { diff --git a/enterprise/frontend/src/embedding-sdk-package/data-app-dev-entry.tsx b/enterprise/frontend/src/embedding-sdk-package/data-app-dev-entry.tsx index 92519883551..0802f5da416 100644 --- a/enterprise/frontend/src/embedding-sdk-package/data-app-dev-entry.tsx +++ b/enterprise/frontend/src/embedding-sdk-package/data-app-dev-entry.tsx @@ -1,9 +1,3 @@ -import * as React from "react"; -import * as ReactDOM from "react-dom"; -import * as ReactDOMClient from "react-dom/client"; -import * as ReactDOMServer from "react-dom/server"; -import * as ReactJsxDevRuntime from "react/jsx-dev-runtime"; -import * as ReactJsxRuntime from "react/jsx-runtime"; import * as sdkExports from "@metabase/embedding-sdk-react"; import * as dataAppExports from "@metabase/embedding-sdk-react/data-app"; import { @@ -15,6 +9,13 @@ import { devDiagnostics, instanceConnectionCheck, } from "@metabase/embedding-sdk-react/data-app-dev"; +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import * as ReactDOMClient from "react-dom/client"; +import { createRoot } from "react-dom/client"; +import * as ReactDOMServer from "react-dom/server"; +import * as ReactJsxDevRuntime from "react/jsx-dev-runtime"; +import * as ReactJsxRuntime from "react/jsx-runtime"; import { allowedHosts, appSlug, @@ -24,10 +25,8 @@ import { rebuiltEvent, sdkVersion, } from "virtual:metabase-data-app-dev-config"; -import { createRoot } from "react-dom/client"; import { DATA_APP_PROVIDER_PROP_KEYS } from "metabase-enterprise/data_apps/sandbox/types"; - // The same baseline reset the production iframe loads (`iframe-vendors.ts`), so the // dev preview matches production. style-loader injects it at runtime. import "metabase-enterprise/data_apps/sandbox/iframe-baseline.css"; diff --git a/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/dynamic-query.unit.spec.tsx b/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/dynamic-query.unit.spec.tsx index 1844799d8a3..dba79404563 100644 --- a/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/dynamic-query.unit.spec.tsx +++ b/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/dynamic-query.unit.spec.tsx @@ -1,13 +1,14 @@ -/* eslint-disable import/order */ - +// Register mocks before loading the modules under test. +// oxfmt-ignore import { createMockStore, resetTestState, stagesOf } from "./setup"; -import { TEST_SCHEMA } from "./fixtures"; import { resolveDatasetQuery as resolveDatasetQueryInBundle } from "embedding-sdk-bundle/lib/create-metabase-query"; import { EMBEDDING_SDK_CONFIG } from "metabase/embedding-sdk/config"; import { count, filter, orderBy, sum } from ".."; +import { TEST_SCHEMA } from "./fixtures"; + beforeEach(resetTestState); afterEach(() => { EMBEDDING_SDK_CONFIG.isDataApp = false; diff --git a/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/resolve-dataset-query-validation.unit.spec.tsx b/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/resolve-dataset-query-validation.unit.spec.tsx index 8f16dd372d6..1bba8c15954 100644 --- a/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/resolve-dataset-query-validation.unit.spec.tsx +++ b/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/resolve-dataset-query-validation.unit.spec.tsx @@ -1,12 +1,13 @@ -/* eslint-disable import/order */ - +// Register mocks before loading the modules under test. +// oxfmt-ignore import { createMockStore, resetTestState } from "./setup"; -import { TEST_SCHEMA } from "./fixtures"; import { resolveDatasetQuery as resolveDatasetQueryInBundle } from "embedding-sdk-bundle/lib/create-metabase-query"; import { avg, breakout, filter, orderBy, sum } from ".."; +import { TEST_SCHEMA } from "./fixtures"; + beforeEach(resetTestState); describe("resolveDatasetQuery validation", () => { diff --git a/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/resolve-dataset-query.unit.spec.tsx b/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/resolve-dataset-query.unit.spec.tsx index 1b6378672a1..963e4b3435b 100644 --- a/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/resolve-dataset-query.unit.spec.tsx +++ b/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/resolve-dataset-query.unit.spec.tsx @@ -1,5 +1,5 @@ -/* eslint-disable import/order */ - +// Register mocks before loading the modules under test. +// oxfmt-ignore import { createMockStore, mockFetchTableMetadata, @@ -8,7 +8,6 @@ import { resetTestState, stagesOf, } from "./setup"; -import { TEST_METADATA, TEST_SCHEMA } from "./fixtures"; import { resolveDatasetQuery as resolveDatasetQueryInBundle } from "embedding-sdk-bundle/lib/create-metabase-query"; import { cardApi } from "metabase/api"; @@ -16,6 +15,8 @@ import * as Lib from "metabase-lib"; import { avg, breakout, count, filter, orderBy, sum } from ".."; +import { TEST_METADATA, TEST_SCHEMA } from "./fixtures"; + beforeEach(resetTestState); describe("resolveDatasetQuery", () => { diff --git a/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/type-contract-invalid.ts b/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/type-contract-invalid.ts index 20df24f0f09..389c6013cd3 100644 --- a/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/type-contract-invalid.ts +++ b/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/type-contract-invalid.ts @@ -1,7 +1,3 @@ -/* eslint-disable import/order */ - -import { TEST_SCHEMA } from "./fixtures"; - import type { MetabaseCard } from "metabase/embedding-sdk/types/question"; import type { MetabaseQueryOptions, UseMetabaseQueryObjectResult } from ".."; @@ -12,8 +8,10 @@ import { useMetabaseQuery, useMetabaseQueryObject, } from ".."; -import { useAction, useDataAppAction } from "../../use-action"; import { defineAction, defineQuery } from "../../../../data-app"; +import { useAction, useDataAppAction } from "../../use-action"; + +import { TEST_SCHEMA } from "./fixtures"; type OrdersTable = (typeof TEST_SCHEMA)["tables"]["orders"]; diff --git a/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/type-contract-valid.ts b/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/type-contract-valid.ts index c3c4cd1b3c6..3d1fb4087d8 100644 --- a/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/type-contract-valid.ts +++ b/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/type-contract-valid.ts @@ -1,9 +1,3 @@ -/* eslint-disable import/order */ - -import { TEST_SCHEMA } from "./fixtures"; - -import type { RowValue } from "../../data-schema"; - import type { MetabaseCard } from "metabase/embedding-sdk/types/question"; import type { UseMetabaseQueryObjectResult } from ".."; @@ -16,8 +10,11 @@ import { useMetabaseQuery, useMetabaseQueryObject, } from ".."; -import { useAction, useDataAppAction } from "../../use-action"; import { defineAction, defineQuery } from "../../../../data-app"; +import type { RowValue } from "../../data-schema"; +import { useAction, useDataAppAction } from "../../use-action"; + +import { TEST_SCHEMA } from "./fixtures"; type OrdersTable = (typeof TEST_SCHEMA)["tables"]["orders"]; diff --git a/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/use-metabase-query.unit.spec.tsx b/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/use-metabase-query.unit.spec.tsx index 7b293ba9972..04a66a31278 100644 --- a/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/use-metabase-query.unit.spec.tsx +++ b/enterprise/frontend/src/embedding-sdk-package/hooks/public/use-metabase-query/tests/use-metabase-query.unit.spec.tsx @@ -1,5 +1,5 @@ -/* eslint-disable import/order */ - +// Register mocks before loading the modules under test. +// oxfmt-ignore import { TEST_DATASET_QUERY, createDeferred, @@ -8,7 +8,6 @@ import { resetTestState, stubSdkBundle, } from "./setup"; -import { TEST_SCHEMA } from "./fixtures"; import { act, renderHook, waitFor } from "@testing-library/react"; @@ -19,6 +18,8 @@ import type { DatasetQuery } from "metabase-types/api"; import { filter, useMetabaseQuery, useMetabaseQueryObject } from ".."; import { defineQuery } from "../define-query"; +import { TEST_SCHEMA } from "./fixtures"; + beforeEach(resetTestState); describe("useMetabaseQueryObject", () => { diff --git a/enterprise/frontend/src/embedding-sdk-package/test/getHostedBundleStoryDecorator.tsx b/enterprise/frontend/src/embedding-sdk-package/test/getHostedBundleStoryDecorator.tsx index 223e5d83e1b..4fa4c294056 100644 --- a/enterprise/frontend/src/embedding-sdk-package/test/getHostedBundleStoryDecorator.tsx +++ b/enterprise/frontend/src/embedding-sdk-package/test/getHostedBundleStoryDecorator.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { MantineProvider } from "@mantine/core"; import type { StoryContext, StoryFn } from "@storybook/react"; import { useEffect } from "react"; diff --git a/enterprise/frontend/src/embedding/auth-common/saml.ts b/enterprise/frontend/src/embedding/auth-common/saml.ts index 93362613262..a70de024db5 100644 --- a/enterprise/frontend/src/embedding/auth-common/saml.ts +++ b/enterprise/frontend/src/embedding/auth-common/saml.ts @@ -1,5 +1,5 @@ import * as MetabaseError from "embedding-sdk-shared/errors"; -// eslint-disable-next-line no-restricted-imports -- import type +// eslint-disable-next-line eslint-js/no-restricted-imports -- import type import type { MetabaseEmbeddingSessionToken } from "metabase/embedding-sdk/types/refresh-token"; /* diff --git a/enterprise/frontend/src/metabase-enterprise/data_apps/runtime/iframe-vendors.ts b/enterprise/frontend/src/metabase-enterprise/data_apps/runtime/iframe-vendors.ts index fc1bc12ae8a..f2bbe7641d9 100644 --- a/enterprise/frontend/src/metabase-enterprise/data_apps/runtime/iframe-vendors.ts +++ b/enterprise/frontend/src/metabase-enterprise/data_apps/runtime/iframe-vendors.ts @@ -1,6 +1,4 @@ import "@mantine/core/styles.css"; import "@mantine/dates/styles.css"; - import "metabase-enterprise/data_apps/sandbox/iframe-baseline.css"; - import "metabase/utils/csp-setup"; diff --git a/enterprise/frontend/src/metabase-enterprise/data_apps/runtime/loader.ts b/enterprise/frontend/src/metabase-enterprise/data_apps/runtime/loader.ts index e46111f3251..c89ac7f1061 100644 --- a/enterprise/frontend/src/metabase-enterprise/data_apps/runtime/loader.ts +++ b/enterprise/frontend/src/metabase-enterprise/data_apps/runtime/loader.ts @@ -1,8 +1,8 @@ import * as React from "react"; -import * as ReactJsxRuntime from "react/jsx-runtime"; import * as ReactDOM from "react-dom"; import * as ReactDOMClient from "react-dom/client"; import * as ReactDOMServer from "react-dom/server"; +import * as ReactJsxRuntime from "react/jsx-runtime"; import { pick } from "underscore"; import * as sdkExports from "embedding-sdk-package"; diff --git a/enterprise/frontend/src/metabase-enterprise/dependencies/components/DependencyGraph/DependencyGraph.tsx b/enterprise/frontend/src/metabase-enterprise/dependencies/components/DependencyGraph/DependencyGraph.tsx index f0f09342408..a14d83d3c5c 100644 --- a/enterprise/frontend/src/metabase-enterprise/dependencies/components/DependencyGraph/DependencyGraph.tsx +++ b/enterprise/frontend/src/metabase-enterprise/dependencies/components/DependencyGraph/DependencyGraph.tsx @@ -2,7 +2,6 @@ // chunk. If this module is ever declared side-effect free, the bundler would // drop this import and the graph would render unstyled. import "@xyflow/react/dist/style.css"; - import { Background, Controls, diff --git a/enterprise/frontend/src/metabase-enterprise/embedding_iframe_sdk/auth-manager/AuthManager.ts b/enterprise/frontend/src/metabase-enterprise/embedding_iframe_sdk/auth-manager/AuthManager.ts index 0c19d8c5564..7aa695973c8 100644 --- a/enterprise/frontend/src/metabase-enterprise/embedding_iframe_sdk/auth-manager/AuthManager.ts +++ b/enterprise/frontend/src/metabase-enterprise/embedding_iframe_sdk/auth-manager/AuthManager.ts @@ -1,15 +1,15 @@ +import { + INVALID_AUTH_METHOD, + MetabaseError, +} from "embedding-sdk-shared/errors"; import { connectToInstanceAuthSso, jwtDefaultRefreshTokenFunction, openSamlLoginPopup, validateSession, } from "embedding/auth-common"; -import { - INVALID_AUTH_METHOD, - MetabaseError, -} from "embedding-sdk-shared/errors"; -import type { EmbedAuthManagerContext } from "metabase/embedding/embedding-iframe-sdk/types/auth-manager"; import type { MetabaseEmbeddingSessionToken } from "metabase/embedding-sdk/types/refresh-token"; +import type { EmbedAuthManagerContext } from "metabase/embedding/embedding-iframe-sdk/types/auth-manager"; /** * Manages authentication for embedded Metabase components. diff --git a/enterprise/frontend/src/metabase-enterprise/license/components/BillingInfo/BillingInfo.tsx b/enterprise/frontend/src/metabase-enterprise/license/components/BillingInfo/BillingInfo.tsx index 62dca5dbec1..dd25673397b 100644 --- a/enterprise/frontend/src/metabase-enterprise/license/components/BillingInfo/BillingInfo.tsx +++ b/enterprise/frontend/src/metabase-enterprise/license/components/BillingInfo/BillingInfo.tsx @@ -51,10 +51,11 @@ const BillingInfoError = () => {
{t`Need help?`} {t`You can ask for billing help at `} - {/* eslint-disable-next-line i18next/no-literal-string */} + {/* eslint-disable i18next/no-literal-string -- untranslated billing address */} billing@metabase.com + {/* eslint-enable i18next/no-literal-string */} @@ -90,10 +91,11 @@ const BillingInfoNotStoreManaged = () => { description={ <> {t`To manage your billing preferences, please email `} - {/* eslint-disable-next-line i18next/no-literal-string */} + {/* eslint-disable i18next/no-literal-string -- untranslated billing address */} billing@metabase.com + {/* eslint-enable i18next/no-literal-string */} } /> diff --git a/enterprise/frontend/src/metabase-enterprise/monitor/ai-auditing/mcp-analytics/components/McpEventsTable.tsx b/enterprise/frontend/src/metabase-enterprise/monitor/ai-auditing/mcp-analytics/components/McpEventsTable.tsx index e59613862ee..cb5bef2831b 100644 --- a/enterprise/frontend/src/metabase-enterprise/monitor/ai-auditing/mcp-analytics/components/McpEventsTable.tsx +++ b/enterprise/frontend/src/metabase-enterprise/monitor/ai-auditing/mcp-analytics/components/McpEventsTable.tsx @@ -24,9 +24,9 @@ import { MCP_EVENT_SORT_COLUMNS, type McpEventSortColumn, type McpFilters, + buildEventsQuery, mcpEventColumnKeys, } from "metabase-enterprise/monitor/ai-auditing/mcp-analytics/query-utils"; -import { buildEventsQuery } from "metabase-enterprise/monitor/ai-auditing/mcp-analytics/query-utils"; import type { CardMetadata, MetadataProvider, diff --git a/enterprise/frontend/src/metabase-enterprise/plugins.ts b/enterprise/frontend/src/metabase-enterprise/plugins.ts index b3b3eb90ecd..1036e671f3a 100644 --- a/enterprise/frontend/src/metabase-enterprise/plugins.ts +++ b/enterprise/frontend/src/metabase-enterprise/plugins.ts @@ -4,10 +4,8 @@ import { PLUGIN_IS_EE_BUILD } from "metabase/plugins"; PLUGIN_IS_EE_BUILD.isEEBuild = () => true; import "./shared"; - // PLUGINS THAT DON'T USE hasPremiumFeature (imported immediately): import "./license"; - // PLUGINS THAT USE hasPremiumFeature (import initialization functions): import { initializePlugin as initializeAdvancedPermissions } from "./advanced_permissions"; import { initializePlugin as initializeAiControls } from "./ai-controls"; diff --git a/enterprise/frontend/src/metabase-enterprise/schema_viewer/components/SchemaViewer/SchemaViewer.tsx b/enterprise/frontend/src/metabase-enterprise/schema_viewer/components/SchemaViewer/SchemaViewer.tsx index 3fcd7411453..c91e493f20a 100644 --- a/enterprise/frontend/src/metabase-enterprise/schema_viewer/components/SchemaViewer/SchemaViewer.tsx +++ b/enterprise/frontend/src/metabase-enterprise/schema_viewer/components/SchemaViewer/SchemaViewer.tsx @@ -2,7 +2,6 @@ // chunk. If this module is ever declared side-effect free, the bundler would // drop this import and the graph would render unstyled. import "@xyflow/react/dist/style.css"; - import { Background, Panel, diff --git a/enterprise/frontend/src/metabase-enterprise/sdk-plugins.ts b/enterprise/frontend/src/metabase-enterprise/sdk-plugins.ts index f19d5b38d18..212187ad2ec 100644 --- a/enterprise/frontend/src/metabase-enterprise/sdk-plugins.ts +++ b/enterprise/frontend/src/metabase-enterprise/sdk-plugins.ts @@ -1,23 +1,34 @@ -/* eslint-disable import/order */ +// oxfmt-ignore import { PLUGIN_IS_EE_BUILD } from "metabase/plugins"; +// oxfmt-ignore import { initializeSdkCustomVizPlugin } from "../embedding-sdk-ee/custom-viz/initialize"; +// oxfmt-ignore import { initializeHandleLinkPlugin } from "../embedding-sdk-ee/handle-link/handle-link"; // SETTINGS OVERRIDES: PLUGIN_IS_EE_BUILD.isEEBuild = () => true; +// oxfmt-ignore import "./shared"; // CORE APP PLUGINS THAT USE hasPremiumFeature (import initialization functions): +// oxfmt-ignore import { initializePlugin as initializeContentTranslation } from "./content_translation"; +// oxfmt-ignore import { initializePlugin as initializeEmbedding } from "./embedding"; +// oxfmt-ignore import { initializePlugin as initializeEmbeddingSdk } from "./embedding-sdk"; +// oxfmt-ignore import { initializePlugin as initializeSharing } from "./sharing"; +// oxfmt-ignore import { initializePlugin as initializeTenants } from "./tenants"; +// oxfmt-ignore import { initializePlugin as initializeWhitelabelPlugin } from "./whitelabel"; +// oxfmt-ignore import { initializePlugin as initializeWhitelabelOverridePlugin } from "./whitelabel/sdk-overrides"; +// oxfmt-ignore import { initializePlugin as initializeNotifications } from "../embedding-sdk-ee/notifications"; /** @@ -39,5 +50,7 @@ export function initializePlugins() { // "SDK EE-plugins", that are specific to the embedding sdk. // These only apply to the SDK, not to the core app +// oxfmt-ignore import "../embedding-sdk-ee/auth"; +// oxfmt-ignore import "../embedding-sdk-ee/metabot"; diff --git a/enterprise/frontend/src/metabase-enterprise/transforms-inspector/pages/TransformInspectPage/components/LensSections/GenericSummarySections/GenericSummarySection.tsx b/enterprise/frontend/src/metabase-enterprise/transforms-inspector/pages/TransformInspectPage/components/LensSections/GenericSummarySections/GenericSummarySection.tsx index fea3600648d..6f56d140c92 100644 --- a/enterprise/frontend/src/metabase-enterprise/transforms-inspector/pages/TransformInspectPage/components/LensSections/GenericSummarySections/GenericSummarySection.tsx +++ b/enterprise/frontend/src/metabase-enterprise/transforms-inspector/pages/TransformInspectPage/components/LensSections/GenericSummarySections/GenericSummarySection.tsx @@ -1,10 +1,10 @@ import { useMemo } from "react"; import { msgid, ngettext, t } from "ttag"; -import { Ellipsified } from "metabase/ui"; import { Box, Card, + Ellipsified, SimpleGrid, Stack, Text, diff --git a/enterprise/frontend/src/metabase-enterprise/transforms-inspector/pages/TransformInspectPage/components/LensSections/GenericSummarySections/components/FieldInfoSection/utils.tsx b/enterprise/frontend/src/metabase-enterprise/transforms-inspector/pages/TransformInspectPage/components/LensSections/GenericSummarySections/components/FieldInfoSection/utils.tsx index 583ddea1cee..2ca6a29805d 100644 --- a/enterprise/frontend/src/metabase-enterprise/transforms-inspector/pages/TransformInspectPage/components/LensSections/GenericSummarySections/components/FieldInfoSection/utils.tsx +++ b/enterprise/frontend/src/metabase-enterprise/transforms-inspector/pages/TransformInspectPage/components/LensSections/GenericSummarySections/components/FieldInfoSection/utils.tsx @@ -3,8 +3,8 @@ import { c, t } from "ttag"; import { getFormattedTime } from "metabase/common/components/DateTime"; import { formatNumber } from "metabase/static-viz/lib/numbers"; -import { Ellipsified } from "metabase/ui"; import { + Ellipsified, EntityNameCell, Flex, Text, diff --git a/eslint.config.mjs b/eslint.config.mjs index 44705de3380..c9d95e6566c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,10 +1,8 @@ // @ts-check -import path from "path"; -import { fileURLToPath } from "url"; +import { builtinRules } from "eslint/use-at-your-own-risk"; +import { aliasRules } from "./frontend/lint/oxlint/rule-aliases.mjs"; -import js from "@eslint/js"; import { fixupPluginRules } from "@eslint/compat"; -import globals from "globals"; import tseslint from "typescript-eslint"; import * as babelParser from "@babel/eslint-parser"; import reactPlugin from "eslint-plugin-react"; @@ -12,7 +10,6 @@ import * as reactHooksPlugin from "eslint-plugin-react-hooks"; import importXPlugin from "eslint-plugin-import-x"; import jestPlugin from "eslint-plugin-jest"; import jestDomPlugin from "eslint-plugin-jest-dom"; -import * as jestFormattingPlugin from "eslint-plugin-jest-formatting"; import testingLibraryPlugin from "eslint-plugin-testing-library"; import cypressPlugin from "eslint-plugin-cypress"; import chaiFriendlyPlugin from "eslint-plugin-chai-friendly"; @@ -23,1289 +20,54 @@ import i18nextPlugin from "eslint-plugin-i18next"; import ttagPlugin from "eslint-plugin-ttag"; import boundaries from "eslint-plugin-boundaries"; -import { - SIDE_EFFECT_FREE_PATHS, - SIDE_EFFECT_PATHS, -} from "./frontend/build/shared/rspack/side-effect-free-modules.js"; -import metabasePlugin from "./frontend/lint/eslint-plugin-metabase/index.js"; -import { - NO_MODULE_SIDE_EFFECTS_IGNORES, - NO_MODULE_SIDE_EFFECTS_OPTIONS, -} from "./frontend/lint/no-module-side-effects-options.js"; -import { - elements as boundaryElements, - enforcedRules as boundaryRules, - getPublicApiModules, -} from "./frontend/lint/module-boundaries.mjs"; -const __dirname = path.dirname(fileURLToPath(import.meta.url)); +import metabasePlugin from "./frontend/lint/eslint-plugin-metabase/index.js"; +import policy from "./frontend/lint/config.mjs"; const shouldLintCssModules = process.env.LINT_CSS_MODULES === "true" || process.env.CI; - -const TEST_FILES_NAME_PATTERN_ERROR_MESSAGE = `Please name your test setup and utils files with a ".spec.*" in the filename, or put them under "/tests", e.g. "setup.spec.ts", "MyComponent.setup.spec.ts", or "tests/setup.ts". This is to ensure they won't be imported in the SDK build.`; - -// ttag string extraction only understands contexts chained inline, e.g. c("...").t`...`; -// a c() call stored in a variable silently drops its strings from the .pot file. -const unchainedTtagContextRestriction = { - selector: - "CallExpression[callee.name=c]:not(MemberExpression > CallExpression)", - message: - "Unchained ttag c() — its strings are dropped from the translation template. Chain it inline: c('context').t`...`", -}; - -const DAYJS_RESTRICTED_IMPORT_MESSAGE = - "Please import dayjs from `metabase/dayjs` instead."; -const dayjsRestrictedPath = { - name: "dayjs", - message: DAYJS_RESTRICTED_IMPORT_MESSAGE, -}; -const dayjsRestrictedPattern = { - group: ["dayjs/*"], - message: DAYJS_RESTRICTED_IMPORT_MESSAGE, -}; -const dayjsExtendRestriction = { - selector: - 'CallExpression[callee.object.name="dayjs"][callee.property.name="extend"]', - message: "Register dayjs plugins in `metabase/dayjs`, not locally.", -}; - -const e2eRestrictedConfig = { - paths: [ - { - name: "metabase-types/api/mocks/presets", - message: "Please use e2e/support/cypress_sample_database instead", - }, - ], - patterns: [ - { - group: [ - "**/enterprise/frontend/src/embedding-sdk-package", - "**/enterprise/frontend/src/embedding-sdk-package/*", - ], - message: "Please use SDK package name - '@metabase/embedding-sdk-react'", - }, - ], -}; - -const baseMetabaseRestrictedConfig = { - patterns: [ - { group: ["metabase-enterprise"] }, - { group: ["metabase-enterprise/*"] }, - { group: ["cljs/metabase.lib*"] }, - { group: ["/embedding-sdk-package"] }, - dayjsRestrictedPattern, - ], - paths: [ - dayjsRestrictedPath, - { - name: "react-redux", - importNames: ["useSelector", "useDispatch", "connect"], - message: "Please import from `metabase/redux` instead.", - }, - { - name: "react-router", - message: "Please import routing from `metabase/router` instead.", - }, - { - name: "@mantine/core", - message: "Please import from `metabase/ui` instead.", - }, - { - name: "@emotion/styled", - message: "Please style components using css modules.", - }, - { - name: "@emotion/react", - message: - "Please use components from `metabase/ui` instead and style them using css modules.", - }, - { - name: "@storybook/test", - message: - "Please use `testing-library/react` or `@testing-library/user-event`", - }, - { - name: "reselect", - message: - "Please import from `@reduxjs/toolkit` instead, which re-exports reselect.", - }, - ], -}; - const configs = [ { - ignores: [ - "frontend/src/cljs/**", - "frontend/src/cljs_release/**", - "**/*.d.ts", - "e2e/support/cypress_sample_database.js", - "e2e/support/cypress_sample_instance_data.js", - "e2e/support/assets/**", - "e2e/embedding-sdk-host-apps/**", - "e2e/tmp/**", - "frontend/test/__support__/custom-viz-fixtures/**/*.js", - "**/custom-viz/fixtures/example_custom_viz_plugin/**", - // The data-app dev entry is served verbatim to the consumer's Vite (it - // imports `@metabase/embedding-sdk-react/*` + a virtual config module), so - // it can't be resolved/linted in this repo. - "enterprise/frontend/src/embedding-sdk-package/data-app-dev-entry.tsx", - "node_modules/**", - "**/dist/**", - "**/target/**", - "resources/**", - "**/__snapshots__/**", - ".shadow-cljs/**", - "!.storybook/**", - ], - }, - js.configs.recommended, - { - files: ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"], - languageOptions: { - ecmaVersion: 2020, - sourceType: "module", - globals: { - ...globals.browser, - ...globals.es2020, - ...globals.commonjs, - ...globals.jest, - before: "readonly", - cy: "readonly", - Cypress: "readonly", - }, - parserOptions: { - ecmaFeatures: { - jsx: true, - legacyDecorators: true, - }, - }, - }, plugins: { metabase: metabasePlugin, react: reactPlugin, "react-hooks": reactHooksPlugin, - // Use 'import' namespace (not 'import-x') to maintain compatibility with existing eslint-disable comments import: importXPlugin, "no-only-tests": noOnlyTestsPlugin, ttag: fixupPluginRules(ttagPlugin), - i18next: fixupPluginRules(i18nextPlugin), + i18next: i18nextPlugin, depend: fixupPluginRules(dependPlugin), - }, - settings: { - "import-x/internal-regex": - "^metabase($|/)|^metabase-lib($|/)|^metabase-types($|/)|^metabase-enterprise($|/)|^embedding-sdk-bundle($|/)|^embedding-sdk-shared($|/)|^embedding-sdk-package($|/)|^e2e($|/)|^__support__($|/)|^assets/|^cljs/|^ee-plugins($|/)|^sdk-ee-plugins($|/)|^build-configs/|^docs/", - "import-x/resolver": { - node: true, - webpack: { - config: path.resolve(__dirname, "./rspack.main.config.js"), - typescript: true, - }, - }, - // Also set import/resolver for eslint-module-utils (used by custom rules) - "import/resolver": { - node: true, - webpack: { - config: path.resolve(__dirname, "./rspack.main.config.js"), - typescript: true, - }, - }, - "import-x/ignore": ["\\.css$"], - react: { - version: "detect", - }, - }, - rules: { - // Base ESLint rules - strict: ["error", "never"], - "no-restricted-syntax": [ - "error", - unchainedTtagContextRestriction, - dayjsExtendRestriction, - ], - "no-undef": "error", - "no-var": "warn", - "no-unused-vars": [ - "error", - { - vars: "all", - args: "none", - varsIgnorePattern: "^_.+$", - ignoreRestSiblings: true, - caughtErrors: "none", // Maintain v7 behavior - }, - ], - "no-empty": ["warn", { allowEmptyCatch: true }], - curly: ["warn", "all"], - eqeqeq: ["warn", "smart"], - "prefer-const": ["warn", { destructuring: "all" }], - "no-restricted-globals": ["error", "close"], - "no-useless-escape": "off", - complexity: ["error", { max: 55 }], - "no-console": ["error", { allow: ["warn", "error", "errorBuffer"] }], - - // Import rules - "import/export": "error", - "import/no-duplicates": ["warn", { considerQueryString: true }], - "import/no-default-export": "error", - "import/no-named-as-default": "off", - "import/no-commonjs": "warn", - "import/order": [ - "error", - { - "newlines-between": "always", - alphabetize: { - order: "asc", - orderImportKind: "asc", - caseInsensitive: false, - }, - groups: [ - "builtin", - "external", - "internal", - "parent", - "sibling", - "index", - ], - warnOnUnassignedImports: false, - }, - ], - "sort-imports": [ - "error", - { - ignoreDeclarationSort: true, - }, - ], - - // React rules - ...reactPlugin.configs.recommended.rules, - ...reactPlugin.configs["jsx-runtime"].rules, - "react/no-is-mounted": "error", - "react/prefer-es6-class": "error", - "react/display-name": "warn", - "react/prop-types": "off", - "react/no-did-mount-set-state": "off", - "react/no-did-update-set-state": "off", - "react/no-find-dom-node": "off", - "react/no-children-prop": "error", - "react/no-string-refs": "error", - "react/no-unescaped-entities": "error", - "react/jsx-no-target-blank": "error", - "react/jsx-key": "error", - "react/forbid-component-props": ["error", { forbid: ["sx"] }], - - // React Hooks rules - ...reactHooksPlugin.configs.recommended.rules, - "react-hooks/exhaustive-deps": [ - "warn", - { - additionalHooks: "(useRegisterMetabotContextProvider)", - }, - ], - - "no-only-tests/no-only-tests": [ - "error", - { - block: [ - "describe", - "it", - "context", - "test", - "tape", - "fixture", - "serial", - "Feature", - "Scenario", - "Given", - "And", - "When", - "Then", - ], - }, - ], - - // Custom metabase rules - "metabase/jtag-missing-key": "error", - "metabase/no-unconditional-metabase-links-render": "error", - "metabase/no-color-literals": "error", - "metabase/valid-theme-tokens": "error", - "metabase/no-literal-metabase-strings": "error", - "metabase/no-analytics-import-outside-analytics-files": "error", - "metabase/no-module-level-memoize": "error", - "metabase/no-direct-memoize-import": "error", - - "depend/ban-dependencies": [ - "error", - { - allowed: [ - "underscore", - "lodash.debounce", - "chalk", - "node-fetch", - "js-yaml", - "glob", - "ora", - "cross-fetch", - ], - }, - ], - - ...i18nextPlugin.configs["flat/recommended"].rules, - }, - }, - { - files: [ - "frontend/src/**/*.{js,jsx,ts,tsx}", - "enterprise/frontend/src/**/*.{js,jsx,ts,tsx}", - ], - plugins: { boundaries, - metabase: metabasePlugin, - }, - settings: { - "boundaries/elements": boundaryElements, - "boundaries/ignore": ["**/e2e/**", "test/**"], - "boundaries/dependency-nodes": ["import", "dynamic-import"], - }, - rules: { - "boundaries/element-types": [ - "error", - { - default: "disallow", - rules: boundaryRules, - message: "${file.type} cannot import from ${dependency.type}", - }, - ], - // Modules flagged `enforcePublicApi` in module-boundaries.mjs must be imported through their index. - // Their own files must import relatively. - "metabase/enforce-module-public-api": [ - "error", - { modules: getPublicApiModules() }, - ], - // Every file frontend/src/ and enterprise/frontend/src/ must belong to a declared module. - "boundaries/no-unknown-files": "error", - }, - }, - { - files: ["**/*.js", "**/*.jsx"], - languageOptions: { - parser: babelParser, - parserOptions: { - requireConfigFile: false, - babelOptions: { - babelrc: false, - configFile: false, - presets: ["@babel/preset-react"], - }, - }, - }, - }, - { - files: [ - "**/*.unit.spec.*", - "frontend/src/metabase/admin/**/*", - "frontend/src/metabase/monitor/tools/**/*", - "frontend/src/metabase/setup/**/*", - "enterprise/frontend/src/metabase-enterprise/whitelabel/**/*", - "enterprise/frontend/src/metabase-enterprise/embedding/**/*", - "frontend/lint/**/*", - "**/*.stories.*", - "**/.storybook/*", - "**/stories-data.*", - "e2e/**/*", - "**/tests/*", - "release/**/*", - "rspack.config.js", - "rspack.main.config.js", - "rspack.embedding-sdk-package.config.js", - "rspack.embedding-sdk-bundle.config.js", - ], - rules: { - "metabase/no-color-literals": "off", - "metabase/no-unconditional-metabase-links-render": "off", - "metabase/no-literal-metabase-strings": "off", - }, - }, - { - files: [ - "**/*.spec.*", - "frontend/lint/**/*", - "**/*.stories.*", - "**/stories-data.*", - "e2e/**/*", - "**/tests/*", - "release/**/*", - "rspack.main.config.js", - ], - rules: { - "i18next/no-literal-string": "off", - }, - }, - // ts configs - ...tseslint.configs.recommended.map((config) => ({ - ...config, - files: ["**/*.ts", "**/*.tsx"], - })), - { - files: ["**/*.ts", "**/*.tsx"], - languageOptions: { - parser: tseslint.parser, - }, - rules: { - "metabase/no-unjustified-type-casts": "error", - "prefer-rest-params": "off", - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-this-alias": "off", - "@typescript-eslint/consistent-type-imports": [ - "error", - { fixStyle: "inline-type-imports" }, - ], - "@typescript-eslint/no-import-type-side-effects": "error", - "@typescript-eslint/no-unused-vars": [ - "error", - { - argsIgnorePattern: "^_.+$", - varsIgnorePattern: "^_.+$", - ignoreRestSiblings: true, - destructuredArrayIgnorePattern: "^_.+$", - caughtErrors: "none", - }, - ], - "@typescript-eslint/no-unsafe-declaration-merging": "off", - "no-unused-vars": "off", // Disable base rule for TS files - }, - }, - { - files: [ - "**/*.unit.spec.ts", - "**/*.unit.spec.tsx", - "**/*.unit.spec.js", - "**/*.unit.spec.jsx", - ], - plugins: { jest: jestPlugin, "jest-dom": jestDomPlugin, "testing-library": testingLibraryPlugin, - // @ts-ignore - "jest-formatting": fixupPluginRules(jestFormattingPlugin), - }, - rules: { - ...jestPlugin.configs.recommended.rules, - ...jestDomPlugin.configs.recommended.rules, - ...testingLibraryPlugin.configs.react.rules, - ...jestFormattingPlugin.configs.recommended.rules, - "jest/valid-title": ["error", { ignoreTypeOfDescribeName: true }], - "jest/expect-expect": [ - "error", - { - assertFunctionNames: ["expect*", "assert*"], - additionalTestBlockFunctions: [], - }, - ], - }, - }, - - // Jest formatting for cypress tests - { - files: ["**/*.cy.spec.ts", "**/*.cy.spec.js"], - plugins: { - // @ts-ignore - "jest-formatting": fixupPluginRules(jestFormattingPlugin), - }, - rules: { - ...jestFormattingPlugin.configs.recommended.rules, - }, - }, - { - files: ["e2e/**/*"], - languageOptions: { - globals: { - ...globals.node, - cy: "readonly", - Cypress: "readonly", - context: "readonly", - assert: "readonly", // Chai's assert, bundled with Cypress - }, - }, - plugins: { cypress: cypressPlugin, "chai-friendly": chaiFriendlyPlugin, - // Declared here so the metabase and import rules below also resolve for non-JS/TS e2e files - // which don't match the `**/*.{js,ts,...}` base. - metabase: metabasePlugin, - import: importXPlugin, - }, - rules: { - "metabase/no-unscoped-text-selectors": "error", - "import/no-commonjs": "off", - "metabase/no-color-literals": "off", - "no-console": "off", - "@typescript-eslint/no-namespace": "off", - "cypress/no-assigning-return-values": "error", - "cypress/no-async-tests": "error", - "cypress/no-pause": "error", - // Use chai-friendly version to allow Chai assertions like expect(x).to.be.true - "no-unused-expressions": "off", - "@typescript-eslint/no-unused-expressions": "off", - "chai-friendly/no-unused-expressions": "error", - quotes: ["error", "double", { avoidEscape: true }], - "no-restricted-imports": [ - "error", - { - paths: [...e2eRestrictedConfig.paths, dayjsRestrictedPath], - patterns: [...e2eRestrictedConfig.patterns, dayjsRestrictedPattern], - }, - ], - "import/no-unresolved": [ - "error", - { ignore: ["@metabase/embedding-sdk-react"] }, - ], - "metabase/no-direct-helper-import": "error", - "metabase/no-unsafe-element-filtering": "warn", - "metabase/no-unordered-test-helpers": "error", + "@typescript-eslint": tseslint.plugin, + storybook: storybookPlugin, + "eslint-js": { rules: Object.fromEntries(builtinRules) }, + "import-js": importXPlugin, + "react-js": reactPlugin, + "jest-js": jestPlugin, + "typescript-js": tseslint.plugin, }, }, + { files: ["**/*.js", "**/*.jsx"], languageOptions: { parser: babelParser } }, { - files: ["e2e/**/*.cy.spec.*"], - rules: { - "no-console": "error", - }, - }, - { - files: ["e2e/test-component/**/*.ts", "e2e/test-component/**/*.tsx"], - rules: { - // Component tests may not import `metabase/` code, so the dayjs facade rule does not apply. - "no-restricted-imports": ["error", e2eRestrictedConfig], - "@typescript-eslint/no-restricted-imports": [ - "warn", - { - patterns: [ - { - // There might be things that we might benefit from importing, like `defer`, in those cases we can change the regex here to allow them - regex: "^metabase/(?!utils/promise$|embedding-sdk/test/).*", - allowTypeImports: true, - message: - "We should avoid importing `metabase/` code in the component tests, we might accidentally include CLJS in the dependencies and that can create issues. Component tests should only use the code it needs to test, which is in the package.", - }, - ], - }, - ], - }, - }, - { - files: ["frontend/test/**/*"], - languageOptions: { - globals: { - ...globals.node, - }, - }, - rules: { - "import/no-commonjs": "off", - "metabase/no-color-literals": "off", - }, - }, - { - files: ["release/**/*.ts", "release/**/*.js"], - rules: { - "no-console": "off", - }, - }, - { - // Standalone Node service — console logging is appropriate here. - files: ["frontend/src/static-viz-server/**/*.ts"], - rules: { - "no-console": "off", - }, - }, - { - files: ["frontend/src/metabase/**/*"], - plugins: { - ttag: fixupPluginRules(ttagPlugin), - }, - rules: { - "no-restricted-imports": [ - "error", - { - paths: baseMetabaseRestrictedConfig.paths, - patterns: [ - ...baseMetabaseRestrictedConfig.patterns, - { - group: ["__support__/**", "!__support__/metadata"], - message: TEST_FILES_NAME_PATTERN_ERROR_MESSAGE, - }, - ], - }, - ], - "ttag/no-module-declaration": "error", - "no-restricted-syntax": [ - "error", - unchainedTtagContextRestriction, - dayjsExtendRestriction, - { - selector: "Literal[value=/mb-base-color-/]", - message: - "You may not use base colors in the application, use semantic colors instead. (see colors.module.css)", - }, - ], - }, - }, - { - files: ["frontend/src/metabase/**/*.stories.tsx"], - rules: { - "import/no-default-export": "off", - "no-restricted-imports": "off", - }, - }, - { - // MCP UI app is a standalone SDK consumer — allow embedding-sdk-package imports - // and raw hex color literals (used for MCP host theme fallback values). - files: ["frontend/src/metabase/embedding/mcp/**/*"], - rules: { - "no-restricted-imports": "off", - "metabase/no-color-literals": "off", - }, - }, - { - files: ["frontend/src/metabase/utils/redux/hooks.ts"], - rules: { - "no-restricted-imports": "off", - }, - }, - { - files: [ - "frontend/src/metabase/**/*.spec.*", - "frontend/src/metabase/**/test-utils.*", - "frontend/src/metabase/**/tests/**/*", - "frontend/src/metabase/**/test/**/*", - ], - rules: { - "no-restricted-imports": ["error", baseMetabaseRestrictedConfig], - }, - }, - { - // The router facade is the single seam allowed to import `react-router` - // directly; every other file goes through `metabase/router`. - files: ["frontend/src/metabase/router/**/*"], - rules: { - "no-restricted-imports": "off", - }, - }, - { - // The dayjs facade is the only place that imports `dayjs` and its plugins - // directly; every other file goes through `metabase/dayjs`. - files: ["frontend/src/metabase/dayjs/**/*"], - rules: { - "no-restricted-imports": "off", - "no-restricted-syntax": ["error", unchainedTtagContextRestriction], - }, - }, - { - files: ["frontend/src/metabase/ui/**/*.{ts,tsx}"], - rules: { - "no-restricted-imports": [ - "error", - { - patterns: [ - { group: ["metabase-enterprise"] }, - { group: ["metabase-enterprise/*"] }, - { group: ["cljs/metabase.lib*"] }, - dayjsRestrictedPattern, - ], - paths: [ - { - name: "react-router", - message: "Please import routing from `metabase/router` instead.", - }, - dayjsRestrictedPath, - { - name: "@emotion/styled", - message: "Please style components using css modules.", - }, - { - name: "@emotion/react", - message: - "Please use components from `metabase/ui` instead and style them using css modules.", - }, - ], - }, - ], - }, - }, - { - files: ["frontend/src/metabase/metadata/**/*"], - rules: { - "no-restricted-imports": [ - "error", - { - patterns: [ - { - group: [ - "metabase-enterprise", - "metabase-enterprise/*", - "cljs/metabase.lib*", - "/embedding-sdk-package", - "/embedding-sdk-bundle", - "/embedding-sdk-shared", - "metabase/entities", - "metabase/entities/*", - ], - }, - dayjsRestrictedPattern, - ], - paths: [ - { - name: "react-redux", - importNames: ["useSelector", "useDispatch", "connect"], - message: "Please import from `metabase/redux` instead.", - }, - { - name: "react-router", - message: "Please import routing from `metabase/router` instead.", - }, - dayjsRestrictedPath, - { - name: "@mantine/core", - message: "Please import from `metabase/ui` instead.", - }, - { - name: "@emotion/styled", - message: "Please style components using css modules.", - }, - { - name: "@emotion/react", - message: - "Please use components from `metabase/ui` instead and style them using css modules.", - }, - { - name: "@storybook/test", - message: - "Please use `testing-library/react` or `@testing-library/user-event`", - }, - ], - }, - ], - }, - }, - { - files: ["frontend/src/metabase-lib/**/*"], - plugins: { - ttag: fixupPluginRules(ttagPlugin), - }, - rules: { - "no-restricted-imports": [ - "error", - { - paths: [dayjsRestrictedPath], - patterns: [ - dayjsRestrictedPattern, - { - group: [ - "metabase/*", - "!metabase/dayjs", - "!metabase/env", - "!metabase/utils", - "!metabase/querying", - "!metabase/services", - ], - }, - { - group: [ - "metabase/utils/*", - "!metabase/utils/encoding", - "!metabase/utils/formatting", - "!metabase/utils/number", - "!metabase/utils/time", - "!metabase/utils/time-dayjs", - "!metabase/utils/types", - "!metabase/urls", - "!metabase/utils/clone", - ], - }, - ], - }, - ], - "ttag/no-module-declaration": "error", - }, - }, - { - files: ["frontend/src/embedding-sdk-bundle/**/*"], - plugins: { - ttag: fixupPluginRules(ttagPlugin), - }, - rules: { - "no-restricted-imports": [ - "error", - { - paths: baseMetabaseRestrictedConfig.paths, - patterns: [ - ...baseMetabaseRestrictedConfig.patterns, - { - group: ["__support__/**", "!__support__/metadata"], - message: TEST_FILES_NAME_PATTERN_ERROR_MESSAGE, - }, - { - group: ["metabase/common/components/LogoIcon"], - importNames: ["LogoIcon"], - message: - "Do not use LogoIcon in the SDK. With custom Icon, it doesn't work because it uses the Metabase instance's relative path.", - }, - ], - }, - ], - "ttag/no-module-declaration": "error", - }, - }, - { - files: [ - "frontend/src/embedding-sdk-bundle/**/*.spec.*", - "frontend/src/embedding-sdk-bundle/**/test-utils.*", - "frontend/src/embedding-sdk-bundle/**/tests/**/*", - "frontend/src/embedding-sdk-bundle/**/test/**/*", - ], - rules: { - "no-restricted-imports": ["error", baseMetabaseRestrictedConfig], - }, - }, - { - files: [ - "frontend/src/embedding-sdk-bundle/test/**/*", - "frontend/src/embedding-sdk-shared/test/storybook-themes.ts", - ], - rules: { - "metabase/no-color-literals": "off", - }, - }, - { - files: ["frontend/src/embedding-sdk-shared/**/*"], - plugins: { - ttag: fixupPluginRules(ttagPlugin), - }, - rules: { - "no-restricted-imports": [ - "error", - { - paths: baseMetabaseRestrictedConfig.paths, - patterns: [ - ...baseMetabaseRestrictedConfig.patterns, - { - group: ["__support__/**", "!__support__/metadata"], - message: TEST_FILES_NAME_PATTERN_ERROR_MESSAGE, - }, - ], - }, - ], - "ttag/no-module-declaration": "error", - }, - }, - { - files: ["frontend/src/metabase/query_builder/**/*"], - rules: { - "import/no-cycle": "error", - }, - }, - { - files: ["enterprise/frontend/src/**/*"], - plugins: { - ttag: fixupPluginRules(ttagPlugin), - }, - settings: { - "import-x/resolver": { - node: true, - webpack: { - config: path.resolve( - __dirname, - "./rspack.embedding-sdk-bundle.config.js", - ), - typescript: true, - }, - }, - // Also set import/resolver for eslint-module-utils (used by custom rules) - "import/resolver": { - node: true, - webpack: { - config: path.resolve( - __dirname, - "./rspack.embedding-sdk-bundle.config.js", - ), - typescript: true, - }, - }, - }, - rules: { - "no-restricted-imports": [ - "error", - { - patterns: [ - { group: ["cljs/metabase.lib*"] }, - { - group: ["__support__/**", "!__support__/metadata"], - message: TEST_FILES_NAME_PATTERN_ERROR_MESSAGE, - }, - dayjsRestrictedPattern, - ], - paths: [ - { - name: "react-router", - message: "Please import routing from `metabase/router` instead.", - }, - dayjsRestrictedPath, - { - name: "@mantine/core", - message: "Please import from `metabase/ui` instead.", - }, - { - name: "@storybook/test", - message: - "Please use `testing-library/react` or `@testing-library/user-event`", - }, - { - name: "react-redux", - importNames: ["useSelector", "useDispatch", "connect"], - message: 'Please use "useSdkSelector", "useSdkDispatch"', - }, - ], - }, - ], - "ttag/no-module-declaration": "error", - }, - }, - { - files: [ - "enterprise/frontend/src/embedding-sdk-{package,bundle,shared}/**/*", - ], - rules: { - "no-restricted-imports": [ - "error", - { - patterns: [ - { group: ["cljs/metabase.lib*"] }, - { - group: ["__support__/**"], - message: TEST_FILES_NAME_PATTERN_ERROR_MESSAGE, - }, - ], - paths: [ - { - name: "@mantine/core", - message: "Please import from `metabase/ui` instead.", - }, - { - name: "@storybook/test", - message: - "Please use `testing-library/react` or `@testing-library/user-event`", - }, - { - name: "react-redux", - importNames: ["useSelector", "useDispatch", "connect"], - message: 'Please use "useSdkSelector", "useSdkDispatch"', - }, - { - name: "metabase/redux", - importNames: ["useStore", "useDispatch"], - message: 'Please use "useSdkStore", "useSdkDispatch"', - }, - ], - }, - ], - }, - }, - { - files: [ - "enterprise/frontend/src/**/*.spec.*", - "enterprise/frontend/src/**/test-utils.*", - "enterprise/frontend/src/**/tests/**/*", - "enterprise/frontend/src/**/test/**/*", - ], - rules: { - "no-restricted-imports": [ - "error", - { - patterns: [{ group: ["cljs/metabase.lib*"] }, dayjsRestrictedPattern], - paths: [ - { - name: "react-router", - message: "Please import routing from `metabase/router` instead.", - }, - dayjsRestrictedPath, - { - name: "@mantine/core", - message: "Please import from `metabase/ui` instead.", - }, - { - name: "@storybook/test", - message: - "Please use `testing-library/react` or `@testing-library/user-event`", - }, - { - name: "react-redux", - importNames: ["useSelector", "useDispatch", "connect"], - message: 'Please use "useSdkSelector", "useSdkDispatch"', - }, - ], - }, - ], - }, - }, - { - files: [ - "enterprise/frontend/src/**/.storybook/*", - "enterprise/frontend/src/**/*.stories.tsx", - ], - rules: { - "import/no-default-export": "off", - "no-restricted-imports": "off", - }, - }, - { - files: ["enterprise/frontend/src/embedding-sdk-{package,shared}/**/*"], - ignores: [ - "enterprise/frontend/src/embedding-sdk-package/{bin,cli}/**/*", - "**/.storybook/**", - "**/jest/**", - "**/test/**", - "**/*.spec.{ts,tsx}", - "**/*.stories.{ts,tsx}", - ], - rules: { - "metabase/no-external-references-for-sdk-package-code": [ - "error", - { - allowedPaths: [ - path.join( - __dirname, - "enterprise/frontend/src/embedding-sdk-package", - ), - path.resolve(__dirname, "frontend/src/embedding-sdk-shared"), - ], - }, - ], - }, - }, - { - files: ["enterprise/frontend/src/embedding/auth-common/**/*"], - rules: { - "no-restricted-imports": [ - "error", - { - patterns: [ - { - group: [ - "embedding-sdk-bundle/*", - "metabase/*", - "!embedding-sdk-bundle/errors", - "!embedding-sdk-bundle/types", - ], - message: - "Keep imports in auth-common to a minimum as it is used in embed.js for iframe embedding.", - }, - ], - }, - ], - }, - }, - { - files: ["frontend/**/*.{ts,tsx}"], - rules: { - "@typescript-eslint/no-restricted-imports": [ - "error", - { - paths: [ - { - name: "custom-viz", - allowTypeImports: true, - message: "Please use only type-only imports from 'custom-viz'.", - }, - ], - }, - ], - }, - }, - { - files: ["enterprise/frontend/src/embedding-sdk-package/bin/**/*"], - rules: { - "metabase/no-literal-metabase-strings": "off", - "no-console": "off", - }, - }, - { - files: ["enterprise/frontend/src/embedding-sdk-package/cli/**/*"], - rules: { - "metabase/no-literal-metabase-strings": "off", - "metabase/no-color-literals": "off", - "no-console": "off", - }, - }, - { - files: ["enterprise/frontend/src/custom-viz/src/templates/index.tsx"], - rules: { - "import/no-default-export": "off", - "metabase/no-color-literals": "off", - }, - }, - { - files: ["docs/**/snippets/**/*.{ts,tsx}"], - rules: { - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-var-requires": "off", - "import/no-commonjs": "off", - "import/no-default-export": "off", - "import/order": "off", - "import/no-unresolved": "off", - "metabase/no-color-literals": "off", - }, - }, - { - files: ["frontend/build/**/*.js"], - languageOptions: { - globals: { - ...globals.node, - }, - }, - rules: { - "import/no-commonjs": "off", - }, - }, - { - files: ["frontend/lint/**/*.js", "frontend/lint/**/*.mjs"], - languageOptions: { - globals: { - ...globals.node, - }, - }, - rules: { - "import/no-commonjs": "off", - }, - }, - { - files: ["**/*.stories.tsx", "**/preview.tsx"], - rules: { - "import/no-default-export": "off", - }, - }, - { - files: [ - "*.config.js", - "*.config.mjs", - "rspack.*.js", - "bin/**/*.js", - ".github/scripts/**/*.js", - ".github/scripts/**/*.mjs", - ".github/scripts/**/*.ts", - ".storybook/*.cjs", - ], - languageOptions: { - globals: { - ...globals.node, - }, - }, - rules: { - "import/no-commonjs": "off", - "no-console": "off", - }, - }, - - // ============================================ - // STORYBOOK (uses native flat config from v9) - // ============================================ - ...storybookPlugin.configs["flat/recommended"], - { - files: ["**/*.stories.@(ts|tsx|js|jsx|mjs|cjs)"], - rules: { - // Disable new v9 rule - fixing this is out of scope for eslint upgrade - "storybook/no-renderer-packages": "off", - }, - }, - - // ============================================ - // SIDE-EFFECT-FREE MODULES - // ============================================ - { - // Run the lint on the directories rspack treats as side-effect-free - files: SIDE_EFFECT_FREE_PATHS.map( - (dir) => `${path.relative(__dirname, dir)}/**/*.{ts,tsx,js,jsx}`, - ), - ignores: [ - ...SIDE_EFFECT_PATHS.map((entry) => - entry.endsWith(path.sep) - ? `${path.relative(__dirname, entry)}/**` - : path.relative(__dirname, entry), - ), - ...NO_MODULE_SIDE_EFFECTS_IGNORES, - ], - rules: { - "metabase/no-module-side-effects": [ - "error", - NO_MODULE_SIDE_EFFECTS_OPTIONS, - ], - }, - }, - - // ============================================ - // BASE API OBJECT ACCESS - // ============================================ - { - // Endpoints are injected into the one `Api` object at import time by the - // file that owns them, so they exist only once that file has been - // evaluated. Reaching them by name through the base object works only while - // something else imports the owner, and a side-effect-free api module lets - // production shake the owner away. Consumers go through the owner's - // exports instead. - files: [ - "frontend/src/**/*.{ts,tsx,js,jsx}", - "enterprise/frontend/src/**/*.{ts,tsx,js,jsx}", - ], - rules: { - "metabase/no-base-api-access": [ - "error", - { - // Where an endpoint is declared is a path question: the api module, or a module's `api/` folder or `api.ts`. - allowInjectionIn: [ - `${__dirname}/frontend/src/metabase/api/**`, - "**/api/**", - "**/api.ts", - ], - // Reaching an endpoint by name is never fine in product code, whatever the file is called. - // Test support seeds the cache by endpoint name, after importing the whole api index so every owner has run. - allowReachIn: [ - `${__dirname}/frontend/src/metabase/api/**`, - `${__dirname}/frontend/test/**`, - "**/__support__/**", - "**/*.unit.spec.*", - ], - }, - ], - }, - }, - // ============================================ - // PLUGIN SLOT DECLARATIONS - // ============================================ - { - files: [ - "frontend/src/**/*.{ts,tsx,js,jsx}", - "enterprise/frontend/src/**/*.{ts,tsx,js,jsx}", - ], - ignores: [ - // metabase/api can't import metabase/plugins under the module rules, so PLUGIN_API is declared and reset by hand. - // This is temporary: once plugins can sit below api, the slot uses definePluginSlot and this exemption goes. - "frontend/src/metabase/api/client/request-handlers.ts", - // TODO(no-plugin-slot-outside-plugins-files): reinitialize() leaves these two alone until their modules adopt definePluginSlot. - "frontend/src/metabase/embedding/embedding-iframe-sdk/plugin.ts", - "frontend/src/embedding-sdk-bundle/store/auth/auth.ts", - ], - rules: { - "metabase/no-plugin-slot-outside-plugins-files": "error", - }, + files: ["**/*.ts", "**/*.tsx"], + languageOptions: { parser: tseslint.parser }, }, + ...policy.map((entry) => ({ + ...entry, + ...(entry.rules && { rules: aliasRules(entry.rules) }), + })), ]; - if (shouldLintCssModules) { try { const postcssModulesPlugin = // @ts-expect-error - optional plugin, may not be installed await import("eslint-plugin-postcss-modules"); - /** @type {any} */ + /** @type {import("eslint").Linter.Config} */ const postcssConfig = { files: ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"], plugins: { @@ -1322,7 +84,7 @@ if (shouldLintCssModules) { }; configs.push(postcssConfig); } catch { - // eslint-plugin-postcss-modules not installed + // The CSS-module plugin is optional. } } diff --git a/eslint.config.module-boundaries.mjs b/eslint.config.module-boundaries.mjs index 81e83f7bffa..698efdf6026 100644 --- a/eslint.config.module-boundaries.mjs +++ b/eslint.config.module-boundaries.mjs @@ -4,10 +4,11 @@ import path from "path"; import { fileURLToPath } from "url"; + import boundaries from "eslint-plugin-boundaries"; import react from "eslint-plugin-react"; -import tseslint from "typescript-eslint"; import { globalIgnores, defineConfig } from "eslint/config"; +import tseslint from "typescript-eslint"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -15,6 +16,7 @@ import { elements as boundaryElements, rules as boundaryRules, } from "./frontend/lint/module-boundaries.mjs"; +import { ruleAliases } from "./frontend/lint/oxlint/rule-aliases.mjs"; // dummy plugins to keep eslint from complaining about missing plugins and settings const alwaysPassingRule = { @@ -63,6 +65,12 @@ export default defineConfig([ }, }, plugins: { + ...Object.fromEntries( + Object.values(ruleAliases).map((rule) => [ + rule.split("/")[0], + alwaysPassingPlugin, + ]), + ), ttag: alwaysPassingPlugin, metabase: alwaysPassingPlugin, import: alwaysPassingPlugin, diff --git a/frontend/build/embedding-sdk/rspack/resolve-config.js b/frontend/build/embedding-sdk/rspack/resolve-config.js new file mode 100644 index 00000000000..b17768aac01 --- /dev/null +++ b/frontend/build/embedding-sdk/rspack/resolve-config.js @@ -0,0 +1,18 @@ +const { ENTERPRISE_SRC_PATH } = require("../../shared/rspack/resolve-aliases"); +const { resolve } = require("../../shared/rspack/resolve-config"); +const { EXTERNAL_DEPENDENCIES } = require("../constants/external-dependencies"); + +module.exports = { + resolve: { + ...resolve, + // The SDK bundle ships the enterprise implementations in every edition. + alias: { + ...resolve.alias, + "sdk-ee-plugins": ENTERPRISE_SRC_PATH + "/sdk-plugins", + "sdk-iframe-embedding-ee-plugins": + ENTERPRISE_SRC_PATH + "/sdk-iframe-embedding-plugins", + "ee-overrides": ENTERPRISE_SRC_PATH + "/overrides", + }, + }, + externals: EXTERNAL_DEPENDENCIES, +}; diff --git a/frontend/build/shared/rspack/resolve-aliases.js b/frontend/build/shared/rspack/resolve-aliases.js index 78e1677fb65..cca278c6bac 100644 --- a/frontend/build/shared/rspack/resolve-aliases.js +++ b/frontend/build/shared/rspack/resolve-aliases.js @@ -80,4 +80,4 @@ const RESOLVE_ALIASES = { : SRC_PATH + "/plugins/noop", }; -module.exports = { RESOLVE_ALIASES }; +module.exports = { ENTERPRISE_SRC_PATH, RESOLVE_ALIASES }; diff --git a/frontend/build/shared/rspack/resolve-config.js b/frontend/build/shared/rspack/resolve-config.js new file mode 100644 index 00000000000..f59b69ac51f --- /dev/null +++ b/frontend/build/shared/rspack/resolve-config.js @@ -0,0 +1,15 @@ +const { RESOLVE_ALIASES } = require("./resolve-aliases"); + +module.exports = { + resolve: { + extensions: [".js", ".jsx", ".ts", ".tsx", ".css", ".svg"], + alias: RESOLVE_ALIASES, + fallback: { + buffer: require.resolve("buffer/"), + url: require.resolve("url/"), + events: require.resolve("events/"), + querystring: require.resolve("querystring-es3"), + }, + }, + externals: { canvg: "canvg" }, +}; diff --git a/frontend/lint/OXLINT.md b/frontend/lint/OXLINT.md new file mode 100644 index 00000000000..eca2d4fc388 --- /dev/null +++ b/frontend/lint/OXLINT.md @@ -0,0 +1,38 @@ +# JavaScript and TypeScript linting + +Oxlint runs the JavaScript and TypeScript checks in `bun run lint`, the +staged-file checks and frontend CI, including module boundaries. Oxfmt formats +the code and sorts imports. ESLint stays installed for the plugins oxlint runs +through its JS plugin API, the compatibility tests and `bun run +module-boundaries`, which the weekly stats collector runs. + +Oxfmt moves value imports past side-effect imports when it sorts. A file whose +import order matters at runtime keeps that order with `// oxfmt-ignore` comments. + +## Where the configuration lives + +These files hold the lint configuration. Paths are relative to `frontend/lint` +unless they say otherwise. + +- `oxlint.config.mts` in the repo root is the entry point oxlint loads. +- `config.mjs` holds the rule options, file scopes, globals and settings. Both + engines read it. +- `oxlint/config.mjs` translates that policy into an oxlint config. +- `oxlint/rule-map.json` maps each ESLint rule name to a native oxlint rule or a + JS plugin rule. +- `recommended-rules.json` and `oxlint/rule-defaults.json` are generated + snapshots of the plugin presets and rule defaults. +- `eslint.config.mjs` in the repo root supplies the parsers and plugins for the + ESLint side of the compatibility tests. + +## After a dependency upgrade + +Run `bun run lint-config-update` and review the snapshot changes. `createConfig` +throws on an enabled rule that has no entry in `oxlint/rule-map.json`, so add an +entry for each rule the upgrade enables. Then run `bun run test-oxlint`. + +## Known limits + +The import resolver reuses its results within one CLI run over a fixed tree. +Persistent caching and invalidation for editors or watch mode are not +implemented. diff --git a/frontend/lint/config.mjs b/frontend/lint/config.mjs new file mode 100644 index 00000000000..5db514f4447 --- /dev/null +++ b/frontend/lint/config.mjs @@ -0,0 +1,1216 @@ +// Shared lint policy. Keep ESLint and oxlint file scopes and options together. +import path from "node:path"; +import globals from "globals"; +import presets from "./recommended-rules.json" with { type: "json" }; +import { + SIDE_EFFECT_FREE_PATHS, + SIDE_EFFECT_PATHS, +} from "../build/shared/rspack/side-effect-free-modules.js"; +import { + NO_MODULE_SIDE_EFFECTS_IGNORES, + NO_MODULE_SIDE_EFFECTS_OPTIONS, +} from "./no-module-side-effects-options.js"; +import { + boundarySettings, + boundaryOptions, + getPublicApiModules, +} from "./module-boundaries.mjs"; +const __dirname = path.resolve(import.meta.dirname, "../.."); +const TEST_FILES_NAME_PATTERN_ERROR_MESSAGE = `Please name your test setup and utils files with a ".spec.*" in the filename, or put them under "/tests", e.g. "setup.spec.ts", "MyComponent.setup.spec.ts", or "tests/setup.ts". This is to ensure they won't be imported in the SDK build.`; + +// ttag string extraction only understands contexts chained inline, e.g. c("...").t`...`; +// a c() call stored in a variable silently drops its strings from the .pot file. +const unchainedTtagContextRestriction = { + selector: + "CallExpression[callee.name=c]:not(MemberExpression > CallExpression)", + message: + "Unchained ttag c() — its strings are dropped from the translation template. Chain it inline: c('context').t`...`", +}; + +const DAYJS_RESTRICTED_IMPORT_MESSAGE = + "Please import dayjs from `metabase/dayjs` instead."; +const dayjsRestrictedPath = { + name: "dayjs", + message: DAYJS_RESTRICTED_IMPORT_MESSAGE, +}; +const dayjsRestrictedPattern = { + group: ["dayjs/*"], + message: DAYJS_RESTRICTED_IMPORT_MESSAGE, +}; +const dayjsExtendRestriction = { + selector: + 'CallExpression[callee.object.name="dayjs"][callee.property.name="extend"]', + message: "Register dayjs plugins in `metabase/dayjs`, not locally.", +}; + +const e2eRestrictedConfig = { + paths: [ + { + name: "metabase-types/api/mocks/presets", + message: "Please use e2e/support/cypress_sample_database instead", + }, + ], + patterns: [ + { + group: [ + "**/enterprise/frontend/src/embedding-sdk-package", + "**/enterprise/frontend/src/embedding-sdk-package/*", + ], + message: "Please use SDK package name - '@metabase/embedding-sdk-react'", + }, + ], +}; + +const baseMetabaseRestrictedConfig = { + patterns: [ + { group: ["metabase-enterprise"] }, + { group: ["metabase-enterprise/*"] }, + { group: ["cljs/metabase.lib*"] }, + { group: ["/embedding-sdk-package"] }, + dayjsRestrictedPattern, + ], + paths: [ + dayjsRestrictedPath, + { + name: "react-redux", + importNames: ["useSelector", "useDispatch", "connect"], + message: "Please import from `metabase/redux` instead.", + }, + { + name: "react-router", + message: "Please import routing from `metabase/router` instead.", + }, + { + name: "@mantine/core", + message: "Please import from `metabase/ui` instead.", + }, + { + name: "@emotion/styled", + message: "Please style components using css modules.", + }, + { + name: "@emotion/react", + message: + "Please use components from `metabase/ui` instead and style them using css modules.", + }, + { + name: "@storybook/test", + message: + "Please use `testing-library/react` or `@testing-library/user-event`", + }, + { + name: "reselect", + message: + "Please import from `@reduxjs/toolkit` instead, which re-exports reselect.", + }, + ], +}; + +const configs = [ + { + ignores: [ + "frontend/src/cljs/**", + "frontend/src/cljs_release/**", + "**/*.d.ts", + "e2e/support/cypress_sample_database.js", + "e2e/support/cypress_sample_instance_data.js", + "e2e/support/assets/**", + "e2e/embedding-sdk-host-apps/**", + "e2e/tmp/**", + "frontend/test/__support__/custom-viz-fixtures/**/*.js", + "**/custom-viz/fixtures/example_custom_viz_plugin/**", + // The data-app dev entry is served verbatim to the consumer's Vite (it + // imports `@metabase/embedding-sdk-react/*` + a virtual config module), so + // it can't be resolved/linted in this repo. + "enterprise/frontend/src/embedding-sdk-package/data-app-dev-entry.tsx", + "node_modules/**", + "**/dist/**", + "**/target/**", + "resources/**", + "**/__snapshots__/**", + ".shadow-cljs/**", + "!.storybook/**", + ], + }, + { rules: presets.javascript }, + { + // The custom-viz build script's console global was declared in a file directive. + files: [ + "enterprise/frontend/src/custom-viz/fixtures/build-example-custom-viz.mjs", + ], + languageOptions: { globals: { console: "readonly" } }, + }, + { + files: ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"], + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + globals: { + ...globals.browser, + ...globals.es2025, + ...globals.commonjs, + ...globals.jest, + before: "readonly", + cy: "readonly", + Cypress: "readonly", + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + legacyDecorators: true, + }, + }, + }, + + settings: { + "import-x/internal-regex": + "^metabase($|/)|^metabase-lib($|/)|^metabase-types($|/)|^metabase-enterprise($|/)|^embedding-sdk-bundle($|/)|^embedding-sdk-shared($|/)|^embedding-sdk-package($|/)|^e2e($|/)|^__support__($|/)|^assets/|^cljs/|^ee-plugins($|/)|^sdk-ee-plugins($|/)|^build-configs/|^docs/", + "import-x/resolver": { + node: true, + webpack: { + config: path.resolve( + __dirname, + "./frontend/build/shared/rspack/resolve-config.js", + ), + typescript: true, + }, + }, + // Also set import/resolver for eslint-module-utils (used by custom rules) + "import/resolver": { + node: true, + webpack: { + config: path.resolve( + __dirname, + "./frontend/build/shared/rspack/resolve-config.js", + ), + typescript: true, + }, + }, + "import-x/ignore": ["\\.css$"], + react: { + version: "detect", + }, + }, + rules: { + // Base ESLint rules + strict: ["error", "never"], + "no-restricted-syntax": [ + "error", + unchainedTtagContextRestriction, + dayjsExtendRestriction, + ], + "no-undef": "error", + "no-var": "warn", + "no-unused-vars": [ + "error", + { + vars: "all", + args: "none", + varsIgnorePattern: "^_.+$", + ignoreRestSiblings: true, + caughtErrors: "none", // Maintain v7 behavior + }, + ], + "no-empty": ["warn", { allowEmptyCatch: true }], + curly: ["warn", "all"], + eqeqeq: ["warn", "smart"], + "prefer-const": ["warn", { destructuring: "all" }], + "no-restricted-globals": ["error", "close"], + "no-useless-escape": "off", + complexity: ["error", { max: 55 }], + "no-console": ["error", { allow: ["warn", "error", "errorBuffer"] }], + + // Import rules + "import/export": "error", + "import/no-duplicates": ["warn", { considerQueryString: true }], + "import/no-default-export": "error", + "import/no-named-as-default": "off", + "import/no-commonjs": "warn", + "sort-imports": [ + "error", + { + ignoreDeclarationSort: true, + }, + ], + + // React rules + ...presets.react, + ...presets.jsxRuntime, + "react/no-is-mounted": "error", + "react/prefer-es6-class": "error", + "react/display-name": "warn", + "react/prop-types": "off", + "react/no-did-mount-set-state": "off", + "react/no-did-update-set-state": "off", + "react/no-find-dom-node": "off", + "react/no-children-prop": "error", + "react/no-string-refs": "error", + "react/no-unescaped-entities": "error", + "react/jsx-no-target-blank": "error", + "react/jsx-key": "error", + "react/forbid-component-props": ["error", { forbid: ["sx"] }], + + // React Hooks rules + ...presets.hooks, + "react-hooks/exhaustive-deps": [ + "warn", + { + additionalHooks: "(useRegisterMetabotContextProvider)", + }, + ], + + "no-only-tests/no-only-tests": [ + "error", + { + block: [ + "describe", + "it", + "context", + "test", + "tape", + "fixture", + "serial", + "Feature", + "Scenario", + "Given", + "And", + "When", + "Then", + ], + }, + ], + + // Custom metabase rules + "metabase/jtag-missing-key": "error", + "metabase/no-unconditional-metabase-links-render": "error", + "metabase/no-color-literals": "error", + "metabase/valid-theme-tokens": "error", + "metabase/no-literal-metabase-strings": "error", + "metabase/no-analytics-import-outside-analytics-files": "error", + "metabase/no-module-level-memoize": "error", + "metabase/no-direct-memoize-import": "error", + + "depend/ban-dependencies": [ + "error", + { + allowed: [ + "underscore", + "lodash.debounce", + "chalk", + "node-fetch", + "js-yaml", + "glob", + "ora", + "cross-fetch", + ], + }, + ], + + ...presets.i18next, + }, + }, + { + files: [ + "frontend/src/**/*.{js,jsx,ts,tsx}", + "enterprise/frontend/src/**/*.{js,jsx,ts,tsx}", + ], + + settings: boundarySettings, + rules: { + "boundaries/element-types": ["error", boundaryOptions], + // Modules flagged `enforcePublicApi` in module-boundaries.mjs must be imported through their index. + // Their own files must import relatively. + "metabase/enforce-module-public-api": [ + "error", + { modules: getPublicApiModules() }, + ], + // Every file frontend/src/ and enterprise/frontend/src/ must belong to a declared module. + "boundaries/no-unknown-files": "error", + }, + }, + { + files: ["**/*.js", "**/*.jsx"], + languageOptions: { + parserOptions: { + requireConfigFile: false, + babelOptions: { + babelrc: false, + configFile: false, + presets: ["@babel/preset-react"], + }, + }, + }, + }, + { + files: [ + "**/*.unit.spec.*", + "frontend/src/metabase/admin/**/*", + "frontend/src/metabase/monitor/tools/**/*", + "frontend/src/metabase/setup/**/*", + "enterprise/frontend/src/metabase-enterprise/whitelabel/**/*", + "enterprise/frontend/src/metabase-enterprise/embedding/**/*", + "frontend/lint/**/*", + "**/*.stories.*", + "**/.storybook/*", + "**/stories-data.*", + "e2e/**/*", + "**/tests/*", + "release/**/*", + "rspack.config.js", + "rspack.main.config.js", + "rspack.embedding-sdk-package.config.js", + "rspack.embedding-sdk-bundle.config.js", + ], + rules: { + "metabase/no-color-literals": "off", + "metabase/no-unconditional-metabase-links-render": "off", + "metabase/no-literal-metabase-strings": "off", + }, + }, + { + files: [ + "**/*.spec.*", + "frontend/lint/**/*", + "**/*.stories.*", + "**/stories-data.*", + "e2e/**/*", + "**/tests/*", + "release/**/*", + "rspack.main.config.js", + ], + rules: { + "i18next/no-literal-string": "off", + }, + }, + // ts configs + ...presets.typescript.map((config) => ({ + ...config, + files: ["**/*.ts", "**/*.tsx"], + // ESLint inherits options for severity-only overrides; oxlint resets them. + ...(config.rules?.["prefer-const"] && { + rules: { + ...config.rules, + "prefer-const": [ + config.rules["prefer-const"], + { destructuring: "all" }, + ], + }, + }), + })), + { + files: ["**/*.ts", "**/*.tsx"], + rules: { + "metabase/no-unjustified-type-casts": "error", + "prefer-rest-params": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-this-alias": "off", + "@typescript-eslint/consistent-type-imports": [ + "error", + { fixStyle: "inline-type-imports" }, + ], + "@typescript-eslint/no-import-type-side-effects": "error", + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_.+$", + varsIgnorePattern: "^_.+$", + ignoreRestSiblings: true, + destructuredArrayIgnorePattern: "^_.+$", + caughtErrors: "none", + }, + ], + "@typescript-eslint/no-unsafe-declaration-merging": "off", + "no-unused-vars": "off", // Disable base rule for TS files + }, + }, + { + files: [ + "**/*.unit.spec.ts", + "**/*.unit.spec.tsx", + "**/*.unit.spec.js", + "**/*.unit.spec.jsx", + ], + + rules: { + ...presets.jest, + ...presets.jestDom, + ...presets.testingLibrary, + "jest/valid-title": ["error", { ignoreTypeOfDescribeName: true }], + "jest/expect-expect": [ + "error", + { + assertFunctionNames: ["expect*", "assert*"], + additionalTestBlockFunctions: [], + }, + ], + }, + }, + + { + files: ["e2e/**/*"], + languageOptions: { + globals: { + ...globals.node, + cy: "readonly", + Cypress: "readonly", + context: "readonly", + assert: "readonly", // Chai's assert, bundled with Cypress + }, + }, + + rules: { + "metabase/no-unscoped-text-selectors": "error", + "import/no-commonjs": "off", + "metabase/no-color-literals": "off", + "no-console": "off", + "@typescript-eslint/no-namespace": "off", + "cypress/no-assigning-return-values": "error", + "cypress/no-async-tests": "error", + "cypress/no-pause": "error", + // Use chai-friendly version to allow Chai assertions like expect(x).to.be.true + "no-unused-expressions": "off", + "@typescript-eslint/no-unused-expressions": "off", + "chai-friendly/no-unused-expressions": "error", + quotes: ["error", "double", { avoidEscape: true }], + "no-restricted-imports": [ + "error", + { + paths: [...e2eRestrictedConfig.paths, dayjsRestrictedPath], + patterns: [...e2eRestrictedConfig.patterns, dayjsRestrictedPattern], + }, + ], + "import/no-unresolved": [ + "error", + { ignore: ["@metabase/embedding-sdk-react"] }, + ], + "metabase/no-direct-helper-import": "error", + "metabase/no-unsafe-element-filtering": "warn", + "metabase/no-unordered-test-helpers": "error", + }, + }, + { + files: ["e2e/**/*.cy.spec.*"], + rules: { + // Repeat inherited options for oxlint's override semantics. + "no-console": ["error", { allow: ["warn", "error", "errorBuffer"] }], + }, + }, + { + files: ["e2e/test-component/**/*.ts", "e2e/test-component/**/*.tsx"], + rules: { + // Component tests may not import `metabase/` code, so the dayjs facade rule does not apply. + "no-restricted-imports": ["error", e2eRestrictedConfig], + "@typescript-eslint/no-restricted-imports": [ + "warn", + { + patterns: [ + { + // There might be things that we might benefit from importing, like `defer`, in those cases we can change the regex here to allow them + regex: "^metabase/(?!utils/promise$|embedding-sdk/test/).*", + allowTypeImports: true, + message: + "We should avoid importing `metabase/` code in the component tests, we might accidentally include CLJS in the dependencies and that can create issues. Component tests should only use the code it needs to test, which is in the package.", + }, + ], + }, + ], + }, + }, + { + files: ["frontend/test/**/*"], + languageOptions: { + globals: { + ...globals.node, + }, + }, + rules: { + "import/no-commonjs": "off", + "metabase/no-color-literals": "off", + }, + }, + { + files: ["release/**/*.ts", "release/**/*.js"], + rules: { + "no-console": "off", + }, + }, + { + // Standalone Node service — console logging is appropriate here. + files: ["frontend/src/static-viz-server/**/*.ts"], + rules: { + "no-console": "off", + }, + }, + { + files: ["frontend/src/metabase/**/*"], + + rules: { + "no-restricted-imports": [ + "error", + { + paths: baseMetabaseRestrictedConfig.paths, + patterns: [ + ...baseMetabaseRestrictedConfig.patterns, + { + group: ["__support__/**", "!__support__/metadata"], + message: TEST_FILES_NAME_PATTERN_ERROR_MESSAGE, + }, + ], + }, + ], + "ttag/no-module-declaration": "error", + "no-restricted-syntax": [ + "error", + unchainedTtagContextRestriction, + dayjsExtendRestriction, + { + selector: "Literal[value=/mb-base-color-/]", + message: + "You may not use base colors in the application, use semantic colors instead. (see colors.module.css)", + }, + ], + }, + }, + { + files: ["frontend/src/metabase/**/*.stories.tsx"], + rules: { + "import/no-default-export": "off", + "no-restricted-imports": "off", + }, + }, + { + // MCP UI app is a standalone SDK consumer — allow embedding-sdk-package imports + // and raw hex color literals (used for MCP host theme fallback values). + files: ["frontend/src/metabase/embedding/mcp/**/*"], + rules: { + "no-restricted-imports": "off", + "metabase/no-color-literals": "off", + }, + }, + { + files: ["frontend/src/metabase/utils/redux/hooks.ts"], + rules: { + "no-restricted-imports": "off", + }, + }, + { + files: [ + "frontend/src/metabase/**/*.spec.*", + "frontend/src/metabase/**/test-utils.*", + "frontend/src/metabase/**/tests/**/*", + "frontend/src/metabase/**/test/**/*", + ], + rules: { + "no-restricted-imports": ["error", baseMetabaseRestrictedConfig], + }, + }, + { + // The router facade is the single seam allowed to import `react-router` + // directly; every other file goes through `metabase/router`. + files: ["frontend/src/metabase/router/**/*"], + rules: { + "no-restricted-imports": "off", + }, + }, + { + // The dayjs facade is the only place that imports `dayjs` and its plugins + // directly; every other file goes through `metabase/dayjs`. + files: ["frontend/src/metabase/dayjs/**/*"], + rules: { + "no-restricted-imports": "off", + "no-restricted-syntax": ["error", unchainedTtagContextRestriction], + }, + }, + { + files: ["frontend/src/metabase/ui/**/*.{ts,tsx}"], + rules: { + "no-restricted-imports": [ + "error", + { + patterns: [ + { group: ["metabase-enterprise"] }, + { group: ["metabase-enterprise/*"] }, + { group: ["cljs/metabase.lib*"] }, + dayjsRestrictedPattern, + ], + paths: [ + { + name: "react-router", + message: "Please import routing from `metabase/router` instead.", + }, + dayjsRestrictedPath, + { + name: "@emotion/styled", + message: "Please style components using css modules.", + }, + { + name: "@emotion/react", + message: + "Please use components from `metabase/ui` instead and style them using css modules.", + }, + ], + }, + ], + }, + }, + { + files: ["frontend/src/metabase/metadata/**/*"], + rules: { + "no-restricted-imports": [ + "error", + { + patterns: [ + { + group: [ + "metabase-enterprise", + "metabase-enterprise/*", + "cljs/metabase.lib*", + "/embedding-sdk-package", + "/embedding-sdk-bundle", + "/embedding-sdk-shared", + "metabase/entities", + "metabase/entities/*", + ], + }, + dayjsRestrictedPattern, + ], + paths: [ + { + name: "react-redux", + importNames: ["useSelector", "useDispatch", "connect"], + message: "Please import from `metabase/redux` instead.", + }, + { + name: "react-router", + message: "Please import routing from `metabase/router` instead.", + }, + dayjsRestrictedPath, + { + name: "@mantine/core", + message: "Please import from `metabase/ui` instead.", + }, + { + name: "@emotion/styled", + message: "Please style components using css modules.", + }, + { + name: "@emotion/react", + message: + "Please use components from `metabase/ui` instead and style them using css modules.", + }, + { + name: "@storybook/test", + message: + "Please use `testing-library/react` or `@testing-library/user-event`", + }, + ], + }, + ], + }, + }, + { + files: ["frontend/src/metabase-lib/**/*"], + + rules: { + "no-restricted-imports": [ + "error", + { + paths: [dayjsRestrictedPath], + patterns: [ + dayjsRestrictedPattern, + { + group: [ + "metabase/*", + "!metabase/dayjs", + "!metabase/env", + "!metabase/utils", + "!metabase/querying", + "!metabase/services", + ], + }, + { + group: [ + "metabase/utils/*", + "!metabase/utils/encoding", + "!metabase/utils/formatting", + "!metabase/utils/number", + "!metabase/utils/time", + "!metabase/utils/time-dayjs", + "!metabase/utils/types", + "!metabase/urls", + "!metabase/utils/clone", + ], + }, + ], + }, + ], + "ttag/no-module-declaration": "error", + }, + }, + { + files: ["frontend/src/embedding-sdk-bundle/**/*"], + + rules: { + "no-restricted-imports": [ + "error", + { + paths: baseMetabaseRestrictedConfig.paths, + patterns: [ + ...baseMetabaseRestrictedConfig.patterns, + { + group: ["__support__/**", "!__support__/metadata"], + message: TEST_FILES_NAME_PATTERN_ERROR_MESSAGE, + }, + { + group: ["metabase/common/components/LogoIcon"], + importNames: ["LogoIcon"], + message: + "Do not use LogoIcon in the SDK. With custom Icon, it doesn't work because it uses the Metabase instance's relative path.", + }, + ], + }, + ], + "ttag/no-module-declaration": "error", + }, + }, + { + files: [ + "frontend/src/embedding-sdk-bundle/**/*.spec.*", + "frontend/src/embedding-sdk-bundle/**/test-utils.*", + "frontend/src/embedding-sdk-bundle/**/tests/**/*", + "frontend/src/embedding-sdk-bundle/**/test/**/*", + ], + rules: { + "no-restricted-imports": ["error", baseMetabaseRestrictedConfig], + }, + }, + { + files: [ + "frontend/src/embedding-sdk-bundle/test/**/*", + "frontend/src/embedding-sdk-shared/test/storybook-themes.ts", + ], + rules: { + "metabase/no-color-literals": "off", + }, + }, + { + files: ["frontend/src/embedding-sdk-shared/**/*"], + + rules: { + "no-restricted-imports": [ + "error", + { + paths: baseMetabaseRestrictedConfig.paths, + patterns: [ + ...baseMetabaseRestrictedConfig.patterns, + { + group: ["__support__/**", "!__support__/metadata"], + message: TEST_FILES_NAME_PATTERN_ERROR_MESSAGE, + }, + ], + }, + ], + "ttag/no-module-declaration": "error", + }, + }, + { + files: ["frontend/src/metabase/query_builder/**/*"], + rules: { + "import/no-cycle": "error", + }, + }, + { + files: ["enterprise/frontend/src/**/*"], + + settings: { + "import-x/resolver": { + node: true, + webpack: { + config: path.resolve( + __dirname, + "./frontend/build/embedding-sdk/rspack/resolve-config.js", + ), + typescript: true, + }, + }, + // Also set import/resolver for eslint-module-utils (used by custom rules) + "import/resolver": { + node: true, + webpack: { + config: path.resolve( + __dirname, + "./frontend/build/embedding-sdk/rspack/resolve-config.js", + ), + typescript: true, + }, + }, + }, + rules: { + "no-restricted-imports": [ + "error", + { + patterns: [ + { group: ["cljs/metabase.lib*"] }, + { + group: ["__support__/**", "!__support__/metadata"], + message: TEST_FILES_NAME_PATTERN_ERROR_MESSAGE, + }, + dayjsRestrictedPattern, + ], + paths: [ + { + name: "react-router", + message: "Please import routing from `metabase/router` instead.", + }, + dayjsRestrictedPath, + { + name: "@mantine/core", + message: "Please import from `metabase/ui` instead.", + }, + { + name: "@storybook/test", + message: + "Please use `testing-library/react` or `@testing-library/user-event`", + }, + { + name: "react-redux", + importNames: ["useSelector", "useDispatch", "connect"], + message: 'Please use "useSdkSelector", "useSdkDispatch"', + }, + ], + }, + ], + "ttag/no-module-declaration": "error", + }, + }, + { + files: [ + "enterprise/frontend/src/embedding-sdk-{package,bundle,shared}/**/*", + ], + rules: { + "no-restricted-imports": [ + "error", + { + patterns: [ + { group: ["cljs/metabase.lib*"] }, + { + group: ["__support__/**"], + message: TEST_FILES_NAME_PATTERN_ERROR_MESSAGE, + }, + ], + paths: [ + { + name: "@mantine/core", + message: "Please import from `metabase/ui` instead.", + }, + { + name: "@storybook/test", + message: + "Please use `testing-library/react` or `@testing-library/user-event`", + }, + { + name: "react-redux", + importNames: ["useSelector", "useDispatch", "connect"], + message: 'Please use "useSdkSelector", "useSdkDispatch"', + }, + { + name: "metabase/redux", + importNames: ["useStore", "useDispatch"], + message: 'Please use "useSdkStore", "useSdkDispatch"', + }, + ], + }, + ], + }, + }, + { + files: [ + "enterprise/frontend/src/**/*.spec.*", + "enterprise/frontend/src/**/test-utils.*", + "enterprise/frontend/src/**/tests/**/*", + "enterprise/frontend/src/**/test/**/*", + ], + rules: { + "no-restricted-imports": [ + "error", + { + patterns: [{ group: ["cljs/metabase.lib*"] }, dayjsRestrictedPattern], + paths: [ + { + name: "react-router", + message: "Please import routing from `metabase/router` instead.", + }, + dayjsRestrictedPath, + { + name: "@mantine/core", + message: "Please import from `metabase/ui` instead.", + }, + { + name: "@storybook/test", + message: + "Please use `testing-library/react` or `@testing-library/user-event`", + }, + { + name: "react-redux", + importNames: ["useSelector", "useDispatch", "connect"], + message: 'Please use "useSdkSelector", "useSdkDispatch"', + }, + ], + }, + ], + }, + }, + { + files: [ + "enterprise/frontend/src/**/.storybook/*", + "enterprise/frontend/src/**/*.stories.tsx", + ], + rules: { + "import/no-default-export": "off", + "no-restricted-imports": "off", + }, + }, + { + files: ["enterprise/frontend/src/embedding-sdk-{package,shared}/**/*"], + ignores: [ + "enterprise/frontend/src/embedding-sdk-package/{bin,cli}/**/*", + "**/.storybook/**", + "**/jest/**", + "**/test/**", + "**/*.spec.{ts,tsx}", + "**/*.stories.{ts,tsx}", + ], + rules: { + "metabase/no-external-references-for-sdk-package-code": [ + "error", + { + allowedPaths: [ + path.join( + __dirname, + "enterprise/frontend/src/embedding-sdk-package", + ), + path.resolve(__dirname, "frontend/src/embedding-sdk-shared"), + ], + }, + ], + }, + }, + { + files: ["enterprise/frontend/src/embedding/auth-common/**/*"], + rules: { + "no-restricted-imports": [ + "error", + { + patterns: [ + { + group: [ + "embedding-sdk-bundle/*", + "metabase/*", + "!embedding-sdk-bundle/errors", + "!embedding-sdk-bundle/types", + ], + message: + "Keep imports in auth-common to a minimum as it is used in embed.js for iframe embedding.", + }, + ], + }, + ], + }, + }, + { + files: ["frontend/**/*.{ts,tsx}"], + rules: { + "@typescript-eslint/no-restricted-imports": [ + "error", + { + paths: [ + { + name: "custom-viz", + allowTypeImports: true, + message: "Please use only type-only imports from 'custom-viz'.", + }, + ], + }, + ], + }, + }, + { + files: ["enterprise/frontend/src/embedding-sdk-package/bin/**/*"], + rules: { + "metabase/no-literal-metabase-strings": "off", + "no-console": "off", + }, + }, + { + files: ["enterprise/frontend/src/embedding-sdk-package/cli/**/*"], + rules: { + "metabase/no-literal-metabase-strings": "off", + "metabase/no-color-literals": "off", + "no-console": "off", + }, + }, + { + files: ["enterprise/frontend/src/custom-viz/src/templates/index.tsx"], + rules: { + "import/no-default-export": "off", + "metabase/no-color-literals": "off", + }, + }, + { + files: ["docs/**/snippets/**/*.{ts,tsx}"], + rules: { + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-var-requires": "off", + "import/no-commonjs": "off", + "import/no-default-export": "off", + "import/no-unresolved": "off", + "metabase/no-color-literals": "off", + }, + }, + { + files: ["frontend/build/**/*.js"], + languageOptions: { + globals: { + ...globals.node, + }, + }, + rules: { + "import/no-commonjs": "off", + }, + }, + { + files: ["frontend/lint/**/*.js", "frontend/lint/**/*.mjs"], + languageOptions: { + globals: { + ...globals.node, + }, + }, + rules: { + "import/no-commonjs": "off", + }, + }, + { + files: ["**/*.stories.tsx", "**/preview.tsx"], + rules: { + "import/no-default-export": "off", + }, + }, + { + files: [ + "*.config.js", + "*.config.mjs", + "rspack.*.js", + "bin/**/*.js", + ".github/scripts/**/*.js", + ".github/scripts/**/*.mjs", + ".github/scripts/**/*.ts", + ".storybook/*.cjs", + ], + languageOptions: { + globals: { + ...globals.node, + }, + }, + rules: { + "import/no-commonjs": "off", + "no-console": "off", + }, + }, + + // ============================================ + // STORYBOOK (uses native flat config from v9) + // ============================================ + ...presets.storybook, + { + files: ["**/*.stories.@(ts|tsx|js|jsx|mjs|cjs)"], + rules: { + // Disable new v9 rule - fixing this is out of scope for eslint upgrade + "storybook/no-renderer-packages": "off", + }, + }, + + // ============================================ + // SIDE-EFFECT-FREE MODULES + // ============================================ + { + // Run the lint on the directories rspack treats as side-effect-free + files: SIDE_EFFECT_FREE_PATHS.map( + (dir) => `${path.relative(__dirname, dir)}/**/*.{ts,tsx,js,jsx}`, + ), + ignores: [ + ...SIDE_EFFECT_PATHS.map((entry) => + entry.endsWith(path.sep) + ? `${path.relative(__dirname, entry)}/**` + : path.relative(__dirname, entry), + ), + ...NO_MODULE_SIDE_EFFECTS_IGNORES, + ], + rules: { + "metabase/no-module-side-effects": [ + "error", + NO_MODULE_SIDE_EFFECTS_OPTIONS, + ], + }, + }, + + // ============================================ + // BASE API OBJECT ACCESS + // ============================================ + { + // Endpoints are injected into the one `Api` object at import time by the + // file that owns them, so they exist only once that file has been + // evaluated. Reaching them by name through the base object works only while + // something else imports the owner, and a side-effect-free api module lets + // production shake the owner away. Consumers go through the owner's + // exports instead. + files: [ + "frontend/src/**/*.{ts,tsx,js,jsx}", + "enterprise/frontend/src/**/*.{ts,tsx,js,jsx}", + ], + rules: { + "metabase/no-base-api-access": [ + "error", + { + // Where an endpoint is declared is a path question: the api module, or a module's `api/` folder or `api.ts`. + allowInjectionIn: [ + `${__dirname}/frontend/src/metabase/api/**`, + "**/api/**", + "**/api.ts", + ], + // Reaching an endpoint by name is never fine in product code, whatever the file is called. + // Test support seeds the cache by endpoint name, after importing the whole api index so every owner has run. + allowReachIn: [ + `${__dirname}/frontend/src/metabase/api/**`, + `${__dirname}/frontend/test/**`, + "**/__support__/**", + "**/*.unit.spec.*", + ], + }, + ], + }, + }, + // ============================================ + // PLUGIN SLOT DECLARATIONS + // ============================================ + { + files: [ + "frontend/src/**/*.{ts,tsx,js,jsx}", + "enterprise/frontend/src/**/*.{ts,tsx,js,jsx}", + ], + ignores: [ + // metabase/api can't import metabase/plugins under the module rules, so PLUGIN_API is declared and reset by hand. + // This is temporary: once plugins can sit below api, the slot uses definePluginSlot and this exemption goes. + "frontend/src/metabase/api/client/request-handlers.ts", + // TODO(no-plugin-slot-outside-plugins-files): reinitialize() leaves these two alone until their modules adopt definePluginSlot. + "frontend/src/metabase/embedding/embedding-iframe-sdk/plugin.ts", + "frontend/src/embedding-sdk-bundle/store/auth/auth.ts", + ], + rules: { + "metabase/no-plugin-slot-outside-plugins-files": "error", + }, + }, +]; + +export default configs; diff --git a/frontend/lint/eslint-plugin-metabase/rules/no-base-api-access.js b/frontend/lint/eslint-plugin-metabase/rules/no-base-api-access.js index af2b123e0be..6d88b1cf3da 100644 --- a/frontend/lint/eslint-plugin-metabase/rules/no-base-api-access.js +++ b/frontend/lint/eslint-plugin-metabase/rules/no-base-api-access.js @@ -8,6 +8,17 @@ const micromatch = require("micromatch"); +const matchers = new WeakMap(); +function matcherFor(globs) { + let matcher = matchers.get(globs); + if (!matcher) { + // Checkouts under dotted directories must match `**`. + matcher = micromatch.matcher(globs, { dot: true }); + matchers.set(globs, matcher); + } + return matcher; +} + // The base api objects. An owner's slice (`cardApi.endpoints`) is the intended way in and is not tracked. const DEFAULT_BASE_APIS = [ { module: "metabase/api", name: "Api" }, @@ -70,14 +81,12 @@ module.exports = { }, create(context) { - const sourceCode = context.sourceCode || context.getSourceCode(); - const filename = context.filename || context.getFilename(); + const { sourceCode, filename } = context; const options = context.options[0] || {}; const baseApis = options.baseApis || DEFAULT_BASE_APIS; - // `dot: true` so a checkout under a dotted directory still matches `**` const isAllowedIn = (globs = []) => - globs.length > 0 && micromatch.isMatch(filename, globs, { dot: true }); + globs.length > 0 && matcherFor(globs)(filename); const injectionAllowed = isAllowedIn(options.allowInjectionIn); const reachAllowed = isAllowedIn(options.allowReachIn); diff --git a/frontend/lint/eslint-plugin-metabase/rules/no-module-side-effects.js b/frontend/lint/eslint-plugin-metabase/rules/no-module-side-effects.js index 3d3b7f3a638..843ace01266 100644 --- a/frontend/lint/eslint-plugin-metabase/rules/no-module-side-effects.js +++ b/frontend/lint/eslint-plugin-metabase/rules/no-module-side-effects.js @@ -218,8 +218,13 @@ module.exports = { const sourceRoots = (options.sourceRoots || DEFAULT_SOURCE_ROOTS).map( (root) => path.resolve(REPO_ROOT, root), ); - // getScope() on the Program node returns the outer global scope, so ask the scope manager for the module scope itself. - const moduleScope = sourceCode.scopeManager.acquire(sourceCode.ast, true); + let moduleScope; + function getModuleScope() { + // Accessing scope constructs the scope tree for the whole file. + // getScope(Program) returns the global scope. + moduleScope ??= sourceCode.scopeManager.acquire(sourceCode.ast, true); + return moduleScope; + } function isInternalModule(source) { return ( @@ -229,8 +234,8 @@ module.exports = { // The import a module-scope name refers to, or null when this file declares the name or it is a global. function importOf(name) { - const def = moduleScope.set - .get(name) + const def = getModuleScope() + .set.get(name) ?.defs.find( (def) => def.type === "ImportBinding" && @@ -245,7 +250,7 @@ module.exports = { } function isDeclaredHere(name) { - return moduleScope.set.has(name) && importOf(name) == null; + return getModuleScope().set.has(name) && importOf(name) == null; } // The annotation may sit on the call or on a wrapper around it (`/* #__PURE__ */ foo()!`). @@ -572,7 +577,7 @@ function resolveSourceFile(base) { return ( candidates.find((candidate) => { try { - return fs.statSync(candidate).isFile(); + return fs.statSync(candidate, { throwIfNoEntry: false })?.isFile(); } catch { return false; } diff --git a/frontend/lint/eslint-plugin-metabase/rules/no-unjustified-type-casts.js b/frontend/lint/eslint-plugin-metabase/rules/no-unjustified-type-casts.js index eec8ea11e6c..38bae6fb9dd 100644 --- a/frontend/lint/eslint-plugin-metabase/rules/no-unjustified-type-casts.js +++ b/frontend/lint/eslint-plugin-metabase/rules/no-unjustified-type-casts.js @@ -24,7 +24,7 @@ module.exports = { }, create(context) { - const sourceCode = context.sourceCode || context.getSourceCode(); + const { sourceCode } = context; function checkCast(node) { if (isConstAssertion(node)) { diff --git a/frontend/lint/module-boundaries.mjs b/frontend/lint/module-boundaries.mjs index 3acc5b82f56..76df9a23fb5 100644 --- a/frontend/lint/module-boundaries.mjs +++ b/frontend/lint/module-boundaries.mjs @@ -570,3 +570,14 @@ export { getFeatureModules, getPublicApiModules, }; + +export const boundarySettings = { + "boundaries/elements": elements, + "boundaries/ignore": ["**/e2e/**", "test/**"], + "boundaries/dependency-nodes": ["import", "dynamic-import"], +}; +export const boundaryOptions = { + default: "disallow", + rules: enforcedRules, + message: "${file.type} cannot import from ${dependency.type}", +}; diff --git a/frontend/lint/oxlint-boundaries.mjs b/frontend/lint/oxlint-boundaries.mjs new file mode 100644 index 00000000000..72fe4a5012f --- /dev/null +++ b/frontend/lint/oxlint-boundaries.mjs @@ -0,0 +1,196 @@ +import path from "node:path"; + +import micromatch from "micromatch"; + +import { + elements, + enforcedRules, + boundarySettings, + boundaryOptions, +} from "./module-boundaries.mjs"; + +const rootPath = path.resolve(import.meta.dirname, "../.."); +const ignorePatterns = boundarySettings["boundaries/ignore"]; +const defaultPolicy = boundaryOptions.default; +const message = boundaryOptions.message; + +const normalize = (value) => value.replaceAll("\\", "/"); + +export function createBoundaryChecker({ elements, rules, rootPath }) { + const regexes = new Map(); + const compile = (pattern) => { + if (typeof pattern !== "string") { + throw new Error("Boundary selectors must be strings"); + } + if (!regexes.has(pattern)) { + regexes.set(pattern, micromatch.makeRe(pattern)); + } + return regexes.get(pattern); + }; + const descriptors = elements.map((element) => { + if ( + element.capture || + element.baseCapture || + element.basePattern || + ![undefined, "folder", "full"].includes(element.mode) || + typeof element.pattern !== "string" + ) { + throw new Error(`Unsupported boundary descriptor: ${element.type}`); + } + return { + ...element, + regex: compile( + element.mode === "full" ? element.pattern : `${element.pattern}/**/*`, + ), + }; + }); + const ordered = [ + ...descriptors.filter((element) => element.mode === "full"), + ...descriptors.filter((element) => element.mode !== "full"), + ]; + const ignores = ignorePatterns.map(compile); + const classifications = new Map(); + + function classify(filename) { + const relativePath = normalize(path.relative(rootPath, filename)); + if (!classifications.has(relativePath)) { + const isIgnored = ignores.some((regex) => regex.test(relativePath)); + const match = isIgnored + ? undefined + : ordered.find((element) => element.regex.test(relativePath)); + classifications.set(relativePath, { + type: match?.type ?? null, + isIgnored, + isUnknown: !match && !isIgnored, + }); + } + return classifications.get(relativePath); + } + + const policies = rules.map((rule) => { + const supported = new Set(["from", "allow", "disallow", "message"]); + if (Object.keys(rule).some((key) => !supported.has(key))) { + throw new Error("Unsupported boundary policy selector"); + } + if (!Array.isArray(rule.from)) { + throw new Error("Boundary policies require a from array"); + } + return { + ...rule, + from: rule.from.map(compile), + allow: (rule.allow ?? []).map(compile), + disallow: (rule.disallow ?? []).map(compile), + }; + }); + const types = [...new Set(elements.map((element) => element.type))]; + const decisions = new Map(); + for (const from of types) { + const destinations = new Map(); + decisions.set(from, destinations); + const applicable = policies.filter((policy) => + policy.from.some((regex) => regex.test(from)), + ); + for (const to of types) { + let allowed = defaultPolicy === "allow"; + let finalMessage = message; + for (const policy of applicable) { + // A denial wins within a policy. + // Later matching policies take precedence. + if (policy.disallow.some((regex) => regex.test(to))) { + allowed = false; + finalMessage = policy.message ?? message; + } else if (policy.allow.some((regex) => regex.test(to))) { + allowed = true; + finalMessage = policy.message ?? message; + } + } + destinations.set(to, { + allowed, + message: finalMessage + .replaceAll("${file.type}", from) + .replaceAll("${dependency.type}", to), + }); + if (from === to && !allowed) { + throw new Error( + "Self-denial requires upstream element-identity semantics", + ); + } + } + } + + function decision(from, to) { + const result = decisions.get(from)?.get(to); + if (!result) { + throw new Error(`Unclassified boundary pair: ${from} -> ${to}`); + } + return result; + } + + return { classify, decision, types }; +} + +const checker = createBoundaryChecker({ + elements, + rules: enforcedRules, + rootPath, +}); +export function createBoundaryPlugin({ resolve }) { + return { + meta: { name: "boundaries", version: "0.0.0-experimental" }, + rules: { + "no-unknown-files": { + meta: { type: "problem", schema: [] }, + create(context) { + if (!checker.classify(context.filename).isUnknown) { + return {}; + } + return { + Program(node) { + context.report({ + node, + message: "File is not of any known element type", + }); + }, + }; + }, + }, + "element-types": { + meta: { type: "problem", schema: [{ type: "object" }] }, + create(context) { + const from = checker.classify(context.filename); + if (!from.type || from.isIgnored) { + return {}; + } + function check(node) { + if (typeof node.value !== "string") { + return; + } + const resolved = resolve(node.value, context); + if (!resolved || normalize(resolved).includes("/node_modules/")) { + return; + } + const to = checker.classify(resolved); + if (!to.type || to.isIgnored) { + return; + } + const result = checker.decision(from.type, to.type); + if (!result.allowed) { + context.report({ node, message: result.message }); + } + } + return { + ImportDeclaration(node) { + check(node.source); + }, + ImportExpression(node) { + check(node.source); + if (node.options) { + check(node.options); + } + }, + }; + }, + }, + }, + }; +} diff --git a/frontend/lint/oxlint-import-resolver.mjs b/frontend/lint/oxlint-import-resolver.mjs new file mode 100644 index 00000000000..110a83f4b0b --- /dev/null +++ b/frontend/lint/oxlint-import-resolver.mjs @@ -0,0 +1,225 @@ +import { existsSync } from "node:fs"; +import { isBuiltin, createRequire } from "node:module"; +import path from "node:path"; + +const require = createRequire(import.meta.url); +const legacyNode = require("eslint-import-resolver-node"); + +function aliases(values = {}) { + return Object.fromEntries( + Object.entries(values).map(([key, value]) => { + const entries = Array.isArray(value) ? value : [value]; + if ( + entries.some( + (entry) => typeof entry !== "string" || !path.isAbsolute(entry), + ) + ) { + throw new Error( + `Unsupported resolver alias: ${key} must contain absolute paths for reuse across package directories`, + ); + } + return [key, entries]; + }), + ); +} + +const isBare = (source) => /^[\w@]/.test(source) && !/[?#!]/.test(source); + +function memoize(resolve, packageRoot) { + const cache = new Map(); + return (source, file) => { + const directory = path.dirname(file); + const key = `${isBare(source) ? packageRoot(directory) : directory}\0${source}`; + if (!cache.has(key)) { + if (cache.size >= 50_000) { + cache.clear(); + } + cache.set(key, resolve(source, file)); + } + return cache.get(key); + }; +} + +// Construct a fresh service after filesystem, dependency or configuration changes. +export function createImportResolverService({ ResolverFactory }) { + const settingsCache = new WeakMap(); + const resolverSettingsCache = new WeakMap(); + const webpackResolvers = new Map(); + const resolvers = new Map(); + const packageRoots = new Map(); + // Absolute aliases and a shared node_modules ancestor make bare imports independent of subdirectory. + function packageRoot(directory) { + if (!packageRoots.has(directory)) { + const parent = path.dirname(directory); + const isRoot = + parent === directory || + existsSync(path.join(directory, "package.json")) || + existsSync(path.join(directory, "node_modules")); + packageRoots.set(directory, isRoot ? directory : packageRoot(parent)); + } + return packageRoots.get(directory); + } + const node = new ResolverFactory({ + extensions: [".mjs", ".cjs", ".js", ".json", ".node"], + conditionNames: ["import", "require", "default"], + mainFields: ["module", "main"], + symlinks: false, + }); + // eslint-import-resolver-node ignores exports and prioritizes module, jsnext:main, then main. + const legacyNative = new ResolverFactory({ + extensions: [".mjs", ".js", ".json", ".node"], + exportsFields: [], + importsFields: [], + mainFields: ["module", "jsnext:main", "main"], + symlinks: false, + }); + const resolveLegacyNode = memoize((source, file) => { + // eslint-import-resolver-node treats queries and fragments as filename characters. + if (/[?#!]/.test(source)) { + return legacyNode.resolve(source, file); + } + if (isBuiltin(source)) return { found: true, path: null }; + const result = legacyNative.sync(path.dirname(file), source); + return result.path ? { found: true, path: result.path } : { found: false }; + }, packageRoot); + + function webpackResolver(configPath) { + if (webpackResolvers.has(configPath)) { + return webpackResolvers.get(configPath); + } + const config = require(configPath); + if (!config || typeof config !== "object" || Array.isArray(config)) { + throw new Error("The resolver adapter needs an object Rspack config"); + } + const supported = new Set(["extensions", "alias", "fallback"]); + for (const key of Object.keys(config.resolve ?? {})) { + if (!supported.has(key)) { + throw new Error(`Unsupported Rspack resolve option: ${key}`); + } + } + const externals = config.externals ?? {}; + if ( + typeof externals !== "object" || + Array.isArray(externals) || + Object.getPrototypeOf(externals) !== Object.prototype + ) { + throw new Error("The resolver adapter needs an object externals map"); + } + const resolver = new ResolverFactory({ + // Match eslint-import-resolver-webpack's enhanced-resolve defaults. + aliasFields: ["browser"], + mainFields: ["browser", "module", "main"], + extensions: config.resolve?.extensions ?? [".js", ".json"], + alias: aliases(config.resolve?.alias), + fallback: aliases(config.resolve?.fallback), + }); + const resolve = (source, file) => { + source = source.slice(source.lastIndexOf("!") + 1); + const query = source.lastIndexOf("?"); + if (query >= 0) { + source = source.slice(0, query); + } + if (Object.hasOwn(externals, source)) { + return { found: true, path: null }; + } + const result = resolver.sync(path.dirname(file), source); + if (result.path) { + return { found: true, path: result.path }; + } + if (isBuiltin(source)) { + return { found: true, path: null }; + } + return { found: false }; + }; + webpackResolvers.set(configPath, resolve); + return resolve; + } + + function forSettings(settings, mode = "import-x") { + if (mode !== "import-x" && mode !== "import") { + throw new Error(`Unsupported resolver mode: ${mode}`); + } + const cached = resolverSettingsCache.get(settings)?.get(mode); + if (cached) { + return cached; + } + for (const key of ["resolver-next", "resolver-legacy", "resolve"]) { + if (`${mode}/${key}` in settings) { + throw new Error(`Unsupported resolver setting: ${mode}/${key}`); + } + } + const config = settings[`${mode}/resolver`]; + if ( + config !== undefined && + (config?.node !== true || + Object.keys(config)[0] !== "node" || + Object.keys(config).some( + (key) => key !== "node" && key !== "webpack", + ) || + (config.webpack && + (typeof config.webpack.config !== "string" || + !path.isAbsolute(config.webpack.config) || + Object.keys(config.webpack).some( + (key) => key !== "config" && key !== "typescript", + )))) + ) { + throw new Error("Unsupported resolver configuration in adapter"); + } + const configPath = config?.webpack?.config; + const key = `${mode}\0${configPath ?? ""}`; + if (!resolvers.has(key)) { + const webpack = configPath ? webpackResolver(configPath) : undefined; + const resolveFallback = (source, file) => { + const result = resolveLegacyNode(source, file); + return result.found ? result : (webpack?.(source, file) ?? result); + }; + const resolveImportX = (source, file) => { + if (isBuiltin(source) || source.startsWith("data:")) { + return { found: true, path: null }; + } + const result = node.sync(path.dirname(file), source); + return result.path + ? { found: true, path: result.path } + : resolveFallback(source, file); + }; + // import-x tries the exports-aware resolver before the node/webpack chain. + const resolve = memoize( + mode === "import-x" ? resolveImportX : resolveFallback, + packageRoot, + ); + resolvers.set(key, { + interfaceVersion: 3, + name: `metabase-oxc-${mode}`, + resolve, + }); + } + const resolver = resolvers.get(key); + if (!resolverSettingsCache.has(settings)) { + resolverSettingsCache.set(settings, new Map()); + } + resolverSettingsCache.get(settings).set(mode, resolver); + return resolver; + } + + function importSettings(settings) { + if (!settingsCache.has(settings)) { + settingsCache.set(settings, { + ...settings, + "import-x/resolver-next": [forSettings(settings)], + }); + } + return settingsCache.get(settings); + } + + function resolveImport(source, context) { + if (context.settings["import/core-modules"]?.includes(source)) { + return null; + } + return forSettings(context.settings, "import").resolve( + source, + context.physicalFilename ?? context.filename, + ).path; + } + + return { forSettings, importSettings, resolveImport }; +} diff --git a/frontend/lint/oxlint/config.mjs b/frontend/lint/oxlint/config.mjs new file mode 100644 index 00000000000..eb50fc4a9c6 --- /dev/null +++ b/frontend/lint/oxlint/config.mjs @@ -0,0 +1,230 @@ +import { createRequire } from "node:module"; +import path from "node:path"; +import { isDeepStrictEqual } from "node:util"; + +import micromatch from "micromatch"; + +import policy from "../config.mjs"; +import { boundarySettings, boundaryOptions } from "../module-boundaries.mjs"; + +import defaults from "./rule-defaults.json" with { type: "json" }; +import ruleMap from "./rule-map.json" with { type: "json" }; + +export const root = path.resolve(import.meta.dirname, "../../.."); + +function merge(left, right) { + const result = { ...left }; + for (const [key, value] of Object.entries(right)) { + result[key] = + value && typeof value === "object" && !Array.isArray(value) + ? merge(result[key] ?? {}, value) + : value; + } + return result; +} + +function options(name, value) { + const [severity, ...supplied] = Array.isArray(value) ? value : [value]; + if (severity === "off" || severity === 0) return "off"; + // Without an options object, native no-unused-vars ignores names matching /^_/. + // ESLint's no-unused-vars with no options reports those names. + const result = [ + ...(defaults[name] ?? (name === "no-unused-vars" ? [{}] : [])), + ]; + supplied.forEach((value, index) => { + result[index] = + value && typeof value === "object" && !Array.isArray(value) + ? merge(result[index] ?? {}, value) + : value; + }); + return [severity, ...result]; +} + +// Oxlint does not apply per-file settings overrides. +const settingsEntries = policy + .filter( + (entry) => + entry.settings || + entry.languageOptions?.parserOptions || + entry.languageOptions?.sourceType, + ) + .map((entry) => ({ + matches: entry.files + ? micromatch.matcher(entry.files, { dot: true }) + : () => true, + sourceType: entry.languageOptions?.sourceType, + settings: entry.settings ?? {}, + parserOptions: entry.languageOptions?.parserOptions ?? {}, + })); +// Files matching the same scopes share one settings object, +// because the resolver adapter keys its WeakMaps by settings identity. +const combinations = new Map(); +let lastFile; +let lastSettings; + +export function settingsForFile(filename) { + if (filename === lastFile) return lastSettings; + const relative = path.relative(root, filename).replaceAll("\\", "/"); + const matched = settingsEntries.flatMap((entry, index) => + entry.matches(relative) ? [index] : [], + ); + const key = matched.join(","); + if (!combinations.has(key)) { + combinations.set( + key, + matched.reduce( + (result, index) => ({ + sourceType: settingsEntries[index].sourceType ?? result.sourceType, + settings: merge(result.settings, settingsEntries[index].settings), + parserOptions: merge( + result.parserOptions, + settingsEntries[index].parserOptions, + ), + }), + { settings: {}, parserOptions: {} }, + ), + ); + } + lastFile = filename; + lastSettings = combinations.get(key); + return lastSettings; +} + +const nativePlugins = ["typescript", "react", "import", "jest"]; +const jsNamespaces = [ + ...new Set( + Object.values(ruleMap) + .filter((name) => name.includes("/")) + .map((name) => name.slice(0, name.indexOf("/"))), + ), +].filter((name) => !nativePlugins.includes(name)); + +export const jsRules = Object.fromEntries( + jsNamespaces + .filter((name) => name !== "no-only-tests") + .map((namespace) => [ + namespace, + [ + ...new Set( + Object.values(ruleMap) + .filter((name) => name.startsWith(`${namespace}/`)) + .map((name) => name.slice(namespace.length + 1)), + ), + ], + ]), +); + +const require = createRequire(import.meta.url); +function hasCssModulesPlugin() { + if (!(process.env.CI || process.env.LINT_CSS_MODULES === "true")) + return false; + try { + require.resolve("eslint-plugin-postcss-modules"); + return true; + } catch { + return false; + } +} + +function glob(pattern) { + // globset's brace alternatives are equivalent to these simple extglobs. + const translated = pattern.replace( + /@\(([^)]+)\)/g, + (_, alternatives) => `{${alternatives.replaceAll("|", ",")}}`, + ); + // Unlike ESLint flat config, oxlint matches a glob without a slash at any depth. + return translated.includes("/") ? translated : `./${translated}`; +} + +export function createConfig() { + const overrides = []; + const ignorePatterns = []; + const configuredExtensions = new Set(); + for (const entry of policy) { + for (const [name, value] of Object.entries(entry.settings ?? {})) { + if ( + name.startsWith("boundaries/") && + (!Object.hasOwn(boundarySettings, name) || + !isDeepStrictEqual(value, boundarySettings[name])) + ) { + throw new Error(`Unsupported boundary checker setting: ${name}`); + } + } + if (entry.ignores && !entry.files) { + ignorePatterns.push(...entry.ignores); + continue; + } + const rules = {}; + for (const [name, value] of Object.entries(entry.rules ?? {})) { + // Native unused-vars accounts for JSX references itself. + if (name === "react/jsx-uses-vars") continue; + const mapped = ruleMap[name]; + const normalized = options(name, value); + if ( + name === "boundaries/element-types" && + normalized !== "off" && + !isDeepStrictEqual(normalized.slice(1), [boundaryOptions]) + ) { + throw new Error( + "The boundary checker requires the shared enforced policy", + ); + } + if (!mapped) { + if (normalized === "off") continue; + throw new Error( + `Map the new lint rule to oxlint or a JS plugin: ${name}`, + ); + } + // A TypeScript extension can share its native rule with the disabled ESLint base rule. + const extension = `@typescript-eslint/${name}`; + if (normalized === "off" && ruleMap[extension] === mapped) { + if (Object.hasOwn(entry.rules, extension)) continue; + // An earlier policy entry enabled the TypeScript extension. + // Turning the shared native rule off here disables it in every file both entries match. + if (configuredExtensions.has(extension)) { + throw new Error( + `Disabling ${name} after ${extension} requires an explicit ${extension} setting in the same policy entry`, + ); + } + } + if (name.startsWith("@typescript-eslint/") && normalized !== "off") { + configuredExtensions.add(name); + } + rules[mapped] = normalized; + } + const globals = Object.fromEntries( + Object.entries(entry.languageOptions?.globals ?? {}).map( + ([name, value]) => [ + name, + value === false ? "readonly" : value === true ? "writable" : value, + ], + ), + ); + overrides.push({ + files: (entry.files ?? ["**/*"]).map(glob), + ...(entry.ignores ? { excludeFiles: entry.ignores.map(glob) } : {}), + ...(Object.keys(globals).length ? { globals } : {}), + rules, + }); + } + const namespaces = [...jsNamespaces]; + if (hasCssModulesPlugin()) { + jsRules["postcss-modules"] = ["no-undef-class"]; + namespaces.push("postcss-modules"); + overrides.push({ + files: ["**/*.{js,jsx,ts,tsx}"], + rules: { "postcss-modules/no-undef-class": "error" }, + }); + } + return { + categories: { correctness: "off" }, + plugins: nativePlugins, + settings: { react: { version: "18.2.0" } }, + ignorePatterns, + overrides, + jsPlugins: namespaces.map((name) => ({ + name, + specifier: path.join(import.meta.dirname, "plugins", `${name}.mjs`), + })), + }; +} diff --git a/frontend/lint/oxlint/jsx-text.mjs b/frontend/lint/oxlint/jsx-text.mjs new file mode 100644 index 00000000000..7bec435db15 --- /dev/null +++ b/frontend/lint/oxlint/jsx-text.mjs @@ -0,0 +1,45 @@ +import { decode } from "html-entities"; + +// JSX uses XML numeric references and requires lowercase x for hexadecimal references. +export function decodeJsxText(text) { + return text.replace( + /&(?:#(\d+)|#x([\da-fA-F]+)|([a-zA-Z][a-zA-Z0-9]*));/g, + (entity, decimal, hexadecimal, named) => { + if (named) return decode(entity, { level: "html4", scope: "strict" }); + const codePoint = Number.parseInt( + decimal ?? hexadecimal, + decimal ? 10 : 16, + ); + return codePoint <= 0x10ffff ? String.fromCodePoint(codePoint) : entity; + }, + ); +} + +// Translation rules expect decoded JSX text. +// Oxlint exposes the source spelling. +export function withDecodedJsxText(plugin) { + const rule = plugin.rules["no-literal-string"]; + return { + ...plugin, + rules: { + ...plugin.rules, + "no-literal-string": { + ...rule, + create(context) { + const visitors = rule.create(context); + return { + ...visitors, + JSXText(node) { + const value = decodeJsxText(node.value); + return visitors.JSXText( + value === node.value + ? node + : Object.create(node, { value: { value } }), + ); + }, + }; + }, + }, + }, + }; +} diff --git a/frontend/lint/oxlint/module-scope.mjs b/frontend/lint/oxlint/module-scope.mjs new file mode 100644 index 00000000000..9c0224a2e59 --- /dev/null +++ b/frontend/lint/oxlint/module-scope.mjs @@ -0,0 +1,39 @@ +import { settingsForFile } from "./config.mjs"; + +// Oxlint parses import-free JS files as scripts, where local bindings can appear to redeclare globals. +// Our policy treats those bindings as module-local. +export function moduleScopeContext(context) { + const sourceCode = context.sourceCode; + if ( + sourceCode.ast.sourceType !== "script" || + settingsForFile(context.filename).sourceType !== "module" + ) + return context; + const views = new WeakMap(); + const getScope = (node) => { + const scope = sourceCode.getScope(node); + if (scope.type !== "global") return scope; + if (!views.has(scope)) { + views.set( + scope, + Object.create(scope, { + variables: { + value: scope.variables.map((variable) => + variable.identifiers.length + ? Object.create(variable, { + eslintImplicitGlobalSetting: { value: undefined }, + }) + : variable, + ), + }, + }), + ); + } + return views.get(scope); + }; + return Object.create(context, { + sourceCode: { + value: Object.create(sourceCode, { getScope: { value: getScope } }), + }, + }); +} diff --git a/frontend/lint/oxlint/plugin.mjs b/frontend/lint/oxlint/plugin.mjs new file mode 100644 index 00000000000..da587dc04cd --- /dev/null +++ b/frontend/lint/oxlint/plugin.mjs @@ -0,0 +1,55 @@ +import { jsRules, settingsForFile } from "./config.mjs"; + +export function wrap( + namespace, + plugin, + transformSettings = (settings) => settings, + transformContext = (context) => context, +) { + // Oxlint reuses one context object per rule for every file it lints. + const contexts = new WeakMap(); + return { + meta: { name: namespace }, + rules: Object.fromEntries( + jsRules[namespace].map((name) => { + const rule = plugin.rules[name]; + if (!rule) + throw new Error(`Missing JS lint rule: ${namespace}/${name}`); + if (typeof rule.create !== "function") { + throw new Error(`Wrapped rule requires create: ${namespace}/${name}`); + } + // Oxlint runs createOnce instead of create whenever the property is present. + // The getters below read context.filename, which throws inside createOnce. + const legacyRule = { ...rule }; + delete legacyRule.createOnce; + return [ + name, + { + ...legacyRule, + create(context) { + let wrapped = contexts.get(context); + if (!wrapped) { + wrapped = transformContext( + Object.create(context, { + settings: { + get: () => + transformSettings( + settingsForFile(context.filename).settings, + ), + }, + parserOptions: { + get: () => + settingsForFile(context.filename).parserOptions, + }, + }), + ); + contexts.set(context, wrapped); + } + return rule.create(wrapped); + }, + }, + ]; + }), + ), + }; +} diff --git a/frontend/lint/oxlint/plugins/boundaries.mjs b/frontend/lint/oxlint/plugins/boundaries.mjs new file mode 100644 index 00000000000..02cc3700f0a --- /dev/null +++ b/frontend/lint/oxlint/plugins/boundaries.mjs @@ -0,0 +1,7 @@ +import { createBoundaryPlugin } from "../../oxlint-boundaries.mjs"; +import { wrap } from "../plugin.mjs"; +import { resolver } from "../resolver.mjs"; +export default wrap( + "boundaries", + createBoundaryPlugin({ resolve: resolver.resolveImport }), +); diff --git a/frontend/lint/oxlint/plugins/chai-friendly.mjs b/frontend/lint/oxlint/plugins/chai-friendly.mjs new file mode 100644 index 00000000000..b9b1f7dd539 --- /dev/null +++ b/frontend/lint/oxlint/plugins/chai-friendly.mjs @@ -0,0 +1,3 @@ +import plugin from "eslint-plugin-chai-friendly"; +import { wrap } from "../plugin.mjs"; +export default wrap("chai-friendly", plugin); diff --git a/frontend/lint/oxlint/plugins/cypress.mjs b/frontend/lint/oxlint/plugins/cypress.mjs new file mode 100644 index 00000000000..15975f67905 --- /dev/null +++ b/frontend/lint/oxlint/plugins/cypress.mjs @@ -0,0 +1,3 @@ +import plugin from "eslint-plugin-cypress"; +import { wrap } from "../plugin.mjs"; +export default wrap("cypress", plugin); diff --git a/frontend/lint/oxlint/plugins/depend.mjs b/frontend/lint/oxlint/plugins/depend.mjs new file mode 100644 index 00000000000..f26b4c3b9d6 --- /dev/null +++ b/frontend/lint/oxlint/plugins/depend.mjs @@ -0,0 +1,4 @@ +import * as imported from "eslint-plugin-depend"; +import { wrap } from "../plugin.mjs"; +const plugin = imported.default ?? imported; +export default wrap("depend", plugin); diff --git a/frontend/lint/oxlint/plugins/eslint-js.mjs b/frontend/lint/oxlint/plugins/eslint-js.mjs new file mode 100644 index 00000000000..a279fca8d9a --- /dev/null +++ b/frontend/lint/oxlint/plugins/eslint-js.mjs @@ -0,0 +1,20 @@ +import { builtinRules } from "eslint/use-at-your-own-risk"; +import { jsRules } from "../config.mjs"; +import { moduleScopeContext } from "../module-scope.mjs"; +import { wrap } from "../plugin.mjs"; +export default wrap("eslint-js", { + rules: Object.fromEntries( + jsRules["eslint-js"].map((name) => { + const rule = builtinRules.get(name); + return [ + name, + name === "no-redeclare" + ? { + ...rule, + create: (context) => rule.create(moduleScopeContext(context)), + } + : rule, + ]; + }), + ), +}); diff --git a/frontend/lint/oxlint/plugins/i18next.mjs b/frontend/lint/oxlint/plugins/i18next.mjs new file mode 100644 index 00000000000..ce29e27cc8c --- /dev/null +++ b/frontend/lint/oxlint/plugins/i18next.mjs @@ -0,0 +1,4 @@ +import plugin from "eslint-plugin-i18next"; +import { withDecodedJsxText } from "../jsx-text.mjs"; +import { wrap } from "../plugin.mjs"; +export default wrap("i18next", withDecodedJsxText(plugin)); diff --git a/frontend/lint/oxlint/plugins/import-js.mjs b/frontend/lint/oxlint/plugins/import-js.mjs new file mode 100644 index 00000000000..ac8c3ad50fd --- /dev/null +++ b/frontend/lint/oxlint/plugins/import-js.mjs @@ -0,0 +1,34 @@ +import { createRequire } from "node:module"; +import plugin from "eslint-plugin-import-x"; +import { wrap } from "../plugin.mjs"; +import { resolver } from "../resolver.mjs"; + +const require = createRequire(import.meta.url); +// import-x needs a working parser for export * analysis. +// Oxlint supplies a stub. +const parser = (typescript) => ({ + meta: { + name: typescript ? "typescript-eslint/parser" : "babel/eslint-parser", + }, + parseForESLint(code, options) { + const implementation = typescript + ? require("typescript-eslint").parser + : require("@babel/eslint-parser"); + return implementation.parseForESLint(code, options); + }, +}); +const tsParser = parser(true); +const jsParser = parser(false); +export default wrap("import-js", plugin, resolver.importSettings, (context) => + Object.create(context, { + languageOptions: { + // Spreading languageOptions eagerly evaluates its AST and global getters. + value: Object.create(context.languageOptions, { + parser: { + get: () => + /\.[cm]?tsx?$/.test(context.filename) ? tsParser : jsParser, + }, + }), + }, + }), +); diff --git a/frontend/lint/oxlint/plugins/jest-dom.mjs b/frontend/lint/oxlint/plugins/jest-dom.mjs new file mode 100644 index 00000000000..bd97a618e51 --- /dev/null +++ b/frontend/lint/oxlint/plugins/jest-dom.mjs @@ -0,0 +1,3 @@ +import plugin from "eslint-plugin-jest-dom"; +import { wrap } from "../plugin.mjs"; +export default wrap("jest-dom", plugin); diff --git a/frontend/lint/oxlint/plugins/jest-js.mjs b/frontend/lint/oxlint/plugins/jest-js.mjs new file mode 100644 index 00000000000..7936913823c --- /dev/null +++ b/frontend/lint/oxlint/plugins/jest-js.mjs @@ -0,0 +1,3 @@ +import plugin from "eslint-plugin-jest"; +import { wrap } from "../plugin.mjs"; +export default wrap("jest-js", plugin); diff --git a/frontend/lint/oxlint/plugins/metabase.mjs b/frontend/lint/oxlint/plugins/metabase.mjs new file mode 100644 index 00000000000..a4f665fe377 --- /dev/null +++ b/frontend/lint/oxlint/plugins/metabase.mjs @@ -0,0 +1,3 @@ +import plugin from "../../eslint-plugin-metabase/index.js"; +import { wrap } from "../plugin.mjs"; +export default wrap("metabase", plugin); diff --git a/frontend/lint/oxlint/plugins/no-only-tests.mjs b/frontend/lint/oxlint/plugins/no-only-tests.mjs new file mode 100644 index 00000000000..3175c58ef11 --- /dev/null +++ b/frontend/lint/oxlint/plugins/no-only-tests.mjs @@ -0,0 +1 @@ +export { default } from "eslint-plugin-no-only-tests"; diff --git a/frontend/lint/oxlint/plugins/postcss-modules.mjs b/frontend/lint/oxlint/plugins/postcss-modules.mjs new file mode 100644 index 00000000000..ff906cfdbe7 --- /dev/null +++ b/frontend/lint/oxlint/plugins/postcss-modules.mjs @@ -0,0 +1,12 @@ +import { fixupPluginRules } from "@eslint/compat"; +import plugin from "eslint-plugin-postcss-modules"; +import { wrap } from "../plugin.mjs"; + +export default wrap( + "postcss-modules", + fixupPluginRules(plugin), + (settings) => ({ + ...settings, + "postcss-modules": { baseDir: "./frontend/src" }, + }), +); diff --git a/frontend/lint/oxlint/plugins/react-js.mjs b/frontend/lint/oxlint/plugins/react-js.mjs new file mode 100644 index 00000000000..013a142f292 --- /dev/null +++ b/frontend/lint/oxlint/plugins/react-js.mjs @@ -0,0 +1,3 @@ +import plugin from "eslint-plugin-react"; +import { wrap } from "../plugin.mjs"; +export default wrap("react-js", plugin); diff --git a/frontend/lint/oxlint/plugins/storybook.mjs b/frontend/lint/oxlint/plugins/storybook.mjs new file mode 100644 index 00000000000..ae402d1d9c3 --- /dev/null +++ b/frontend/lint/oxlint/plugins/storybook.mjs @@ -0,0 +1,3 @@ +import plugin from "eslint-plugin-storybook"; +import { wrap } from "../plugin.mjs"; +export default wrap("storybook", plugin); diff --git a/frontend/lint/oxlint/plugins/testing-library.mjs b/frontend/lint/oxlint/plugins/testing-library.mjs new file mode 100644 index 00000000000..dab25bad80b --- /dev/null +++ b/frontend/lint/oxlint/plugins/testing-library.mjs @@ -0,0 +1,3 @@ +import plugin from "eslint-plugin-testing-library"; +import { wrap } from "../plugin.mjs"; +export default wrap("testing-library", plugin); diff --git a/frontend/lint/oxlint/plugins/ttag.mjs b/frontend/lint/oxlint/plugins/ttag.mjs new file mode 100644 index 00000000000..0e66d5e9819 --- /dev/null +++ b/frontend/lint/oxlint/plugins/ttag.mjs @@ -0,0 +1,4 @@ +import plugin from "eslint-plugin-ttag"; +import { wrap } from "../plugin.mjs"; +import { fixupPluginRules } from "@eslint/compat"; +export default wrap("ttag", fixupPluginRules(plugin)); diff --git a/frontend/lint/oxlint/plugins/typescript-js.mjs b/frontend/lint/oxlint/plugins/typescript-js.mjs new file mode 100644 index 00000000000..728ffda5727 --- /dev/null +++ b/frontend/lint/oxlint/plugins/typescript-js.mjs @@ -0,0 +1,3 @@ +import rules from "@typescript-eslint/eslint-plugin/use-at-your-own-risk/rules"; +import { wrap } from "../plugin.mjs"; +export default wrap("typescript-js", { rules }); diff --git a/frontend/lint/oxlint/resolver.mjs b/frontend/lint/oxlint/resolver.mjs new file mode 100644 index 00000000000..32557c61d49 --- /dev/null +++ b/frontend/lint/oxlint/resolver.mjs @@ -0,0 +1,5 @@ +import { ResolverFactory } from "oxc-resolver"; + +import { createImportResolverService } from "../oxlint-import-resolver.mjs"; + +export const resolver = createImportResolverService({ ResolverFactory }); diff --git a/frontend/lint/oxlint/rule-aliases.mjs b/frontend/lint/oxlint/rule-aliases.mjs new file mode 100644 index 00000000000..af4ad6df12a --- /dev/null +++ b/frontend/lint/oxlint/rule-aliases.mjs @@ -0,0 +1,16 @@ +import ruleMap from "./rule-map.json" with { type: "json" }; + +// Oxlint reserves native namespaces, so retained JS rules need distinct suppression names. +export const ruleAliases = Object.fromEntries( + Object.entries(ruleMap).filter(([, target]) => + /^(?:eslint|import|react|jest|typescript)-js\//.test(target), + ), +); +export function aliasRules(rules = {}) { + return Object.fromEntries( + Object.entries(rules).map(([name, value]) => [ + ruleAliases[name] ?? name, + value, + ]), + ); +} diff --git a/frontend/lint/oxlint/rule-defaults.json b/frontend/lint/oxlint/rule-defaults.json new file mode 100644 index 00000000000..1cfc87e2231 --- /dev/null +++ b/frontend/lint/oxlint/rule-defaults.json @@ -0,0 +1,139 @@ +{ + "getter-return": [ + { + "allowImplicit": false + } + ], + "no-cond-assign": ["except-parens"], + "no-constant-condition": [ + { + "checkLoops": "allExceptWhileTrue" + } + ], + "no-empty": [ + { + "allowEmptyCatch": false + } + ], + "no-empty-pattern": [ + { + "allowObjectPatternsAsParameters": false + } + ], + "no-extra-boolean-cast": [{}], + "no-fallthrough": [ + { + "allowEmptyCase": false, + "reportUnusedFallthroughComment": false + } + ], + "no-global-assign": [ + { + "exceptions": [] + } + ], + "no-invalid-regexp": [{}], + "no-irregular-whitespace": [ + { + "skipComments": false, + "skipJSXText": false, + "skipRegExps": false, + "skipStrings": true, + "skipTemplates": false + } + ], + "no-misleading-character-class": [ + { + "allowEscape": false + } + ], + "no-redeclare": [ + { + "builtinGlobals": true + } + ], + "no-self-assign": [ + { + "props": true + } + ], + "no-shadow-restricted-names": [ + { + "reportGlobalThis": false + } + ], + "no-undef": [ + { + "typeof": false + } + ], + "no-unsafe-negation": [ + { + "enforceForOrderingRelations": false + } + ], + "no-unsafe-optional-chaining": [ + { + "disallowArithmeticOperators": false + } + ], + "no-useless-escape": [ + { + "allowRegexCharacters": [] + } + ], + "use-isnan": [ + { + "enforceForIndexOf": false, + "enforceForSwitchCase": true + } + ], + "valid-typeof": [ + { + "requireStringLiterals": false + } + ], + "strict": ["safe"], + "curly": ["all"], + "prefer-const": [ + { + "destructuring": "any", + "ignoreReadBeforeAssign": false + } + ], + "complexity": [20], + "sort-imports": [ + { + "allowSeparatedGroups": false, + "ignoreCase": false, + "ignoreDeclarationSort": false, + "ignoreMemberSort": false, + "memberSyntaxSortOrder": ["none", "all", "multiple", "single"] + } + ], + "depend/ban-dependencies": [{}], + "no-console": [{}], + "@typescript-eslint/no-unused-expressions": [ + { + "allowShortCircuit": false, + "allowTaggedTemplates": false, + "allowTernary": false + } + ], + "storybook/await-interactions": [], + "storybook/context-in-play-function": [], + "storybook/default-exports": [], + "storybook/hierarchy-separator": [], + "storybook/no-redundant-story-name": [], + "storybook/prefer-pascal-case": [], + "storybook/story-exports": [], + "storybook/use-storybook-expect": [], + "storybook/use-storybook-testing-library": [], + "storybook/no-renderer-packages": [], + "storybook/no-uninstalled-addons": [ + { + "packageJsonLocation": "", + "ignore": [] + } + ] +} diff --git a/frontend/lint/oxlint/rule-map.json b/frontend/lint/oxlint/rule-map.json new file mode 100644 index 00000000000..7748346b160 --- /dev/null +++ b/frontend/lint/oxlint/rule-map.json @@ -0,0 +1,217 @@ +{ + "constructor-super": "constructor-super", + "for-direction": "for-direction", + "getter-return": "getter-return", + "no-async-promise-executor": "no-async-promise-executor", + "no-case-declarations": "no-case-declarations", + "no-class-assign": "no-class-assign", + "no-compare-neg-zero": "no-compare-neg-zero", + "no-cond-assign": "no-cond-assign", + "no-const-assign": "no-const-assign", + "no-constant-binary-expression": "no-constant-binary-expression", + "no-constant-condition": "no-constant-condition", + "no-control-regex": "no-control-regex", + "no-debugger": "no-debugger", + "no-delete-var": "no-delete-var", + "no-dupe-args": "eslint-js/no-dupe-args", + "no-dupe-class-members": "no-dupe-class-members", + "no-dupe-else-if": "no-dupe-else-if", + "no-dupe-keys": "no-dupe-keys", + "no-duplicate-case": "no-duplicate-case", + "no-empty": "no-empty", + "no-empty-character-class": "no-empty-character-class", + "no-empty-pattern": "no-empty-pattern", + "no-empty-static-block": "no-empty-static-block", + "no-ex-assign": "no-ex-assign", + "no-extra-boolean-cast": "no-extra-boolean-cast", + "no-fallthrough": "no-fallthrough", + "no-func-assign": "no-func-assign", + "no-global-assign": "no-global-assign", + "no-import-assign": "no-import-assign", + "no-invalid-regexp": "no-invalid-regexp", + "no-irregular-whitespace": "no-irregular-whitespace", + "no-loss-of-precision": "no-loss-of-precision", + "no-misleading-character-class": "no-misleading-character-class", + "no-new-native-nonconstructor": "no-new-native-nonconstructor", + "no-nonoctal-decimal-escape": "no-nonoctal-decimal-escape", + "no-obj-calls": "no-obj-calls", + "no-octal": "eslint-js/no-octal", + "no-prototype-builtins": "no-prototype-builtins", + "no-redeclare": "eslint-js/no-redeclare", + "no-regex-spaces": "no-regex-spaces", + "no-self-assign": "no-self-assign", + "no-setter-return": "no-setter-return", + "no-shadow-restricted-names": "no-shadow-restricted-names", + "no-sparse-arrays": "no-sparse-arrays", + "no-this-before-super": "no-this-before-super", + "no-undef": "no-undef", + "no-unexpected-multiline": "no-unexpected-multiline", + "no-unreachable": "no-unreachable", + "no-unsafe-finally": "no-unsafe-finally", + "no-unsafe-negation": "no-unsafe-negation", + "no-unsafe-optional-chaining": "eslint-js/no-unsafe-optional-chaining", + "no-unused-labels": "no-unused-labels", + "no-unused-private-class-members": "no-unused-private-class-members", + "no-unused-vars": "no-unused-vars", + "no-useless-backreference": "no-useless-backreference", + "no-useless-catch": "no-useless-catch", + "no-useless-escape": "no-useless-escape", + "no-with": "no-with", + "require-yield": "require-yield", + "use-isnan": "use-isnan", + "valid-typeof": "valid-typeof", + "metabase/no-unscoped-text-selectors": "metabase/no-unscoped-text-selectors", + "cypress/no-assigning-return-values": "cypress/no-assigning-return-values", + "cypress/no-async-tests": "cypress/no-async-tests", + "cypress/no-pause": "cypress/no-pause", + "chai-friendly/no-unused-expressions": "chai-friendly/no-unused-expressions", + "quotes": "eslint-js/quotes", + "no-restricted-imports": "eslint-js/no-restricted-imports", + "import/no-unresolved": "import-js/no-unresolved", + "metabase/no-direct-helper-import": "metabase/no-direct-helper-import", + "metabase/no-unsafe-element-filtering": "metabase/no-unsafe-element-filtering", + "metabase/no-unordered-test-helpers": "metabase/no-unordered-test-helpers", + "strict": "eslint-js/strict", + "no-restricted-syntax": "eslint-js/no-restricted-syntax", + "no-var": "no-var", + "curly": "curly", + "eqeqeq": "eqeqeq", + "prefer-const": "prefer-const", + "no-restricted-globals": "no-restricted-globals", + "complexity": "complexity", + "import/export": "import-js/export", + "import/no-duplicates": "import/no-duplicates", + "import/no-default-export": "import/no-default-export", + "sort-imports": "sort-imports", + "react/display-name": "react/display-name", + "react/jsx-key": "react/jsx-key", + "react/jsx-no-comment-textnodes": "react/jsx-no-comment-textnodes", + "react/jsx-no-duplicate-props": "react/jsx-no-duplicate-props", + "react/jsx-no-target-blank": "react/jsx-no-target-blank", + "react/jsx-no-undef": "react/jsx-no-undef", + "react/no-children-prop": "react/no-children-prop", + "react/no-danger-with-children": "react/no-danger-with-children", + "react/no-deprecated": "react-js/no-deprecated", + "react/no-direct-mutation-state": "react/no-direct-mutation-state", + "react/no-is-mounted": "react/no-is-mounted", + "react/no-render-return-value": "react/no-render-return-value", + "react/no-string-refs": "react/no-string-refs", + "react/no-unescaped-entities": "react/no-unescaped-entities", + "react/no-unknown-property": "react/no-unknown-property", + "react/require-render-return": "react/require-render-return", + "react/prefer-es6-class": "react/prefer-es6-class", + "react/forbid-component-props": "react/forbid-component-props", + "react-hooks/rules-of-hooks": "react/rules-of-hooks", + "react-hooks/exhaustive-deps": "react/exhaustive-deps", + "no-only-tests/no-only-tests": "no-only-tests/no-only-tests", + "metabase/jtag-missing-key": "metabase/jtag-missing-key", + "metabase/valid-theme-tokens": "metabase/valid-theme-tokens", + "metabase/no-analytics-import-outside-analytics-files": "metabase/no-analytics-import-outside-analytics-files", + "metabase/no-module-level-memoize": "metabase/no-module-level-memoize", + "metabase/no-direct-memoize-import": "metabase/no-direct-memoize-import", + "depend/ban-dependencies": "depend/ban-dependencies", + "jest/expect-expect": "jest/expect-expect", + "jest/no-alias-methods": "jest/no-alias-methods", + "jest/no-commented-out-tests": "jest/no-commented-out-tests", + "jest/no-conditional-expect": "jest-js/no-conditional-expect", + "jest/no-deprecated-functions": "jest/no-deprecated-functions", + "jest/no-disabled-tests": "jest/no-disabled-tests", + "jest/no-done-callback": "jest/no-done-callback", + "jest/no-export": "jest/no-export", + "jest/no-focused-tests": "jest/no-focused-tests", + "jest/no-identical-title": "jest/no-identical-title", + "jest/no-interpolation-in-snapshots": "jest/no-interpolation-in-snapshots", + "jest/no-jasmine-globals": "jest/no-jasmine-globals", + "jest/no-mocks-import": "jest/no-mocks-import", + "jest/no-standalone-expect": "jest/no-standalone-expect", + "jest/no-test-prefixes": "jest/no-test-prefixes", + "jest/valid-describe-callback": "jest/valid-describe-callback", + "jest/valid-expect": "jest-js/valid-expect", + "jest/valid-expect-in-promise": "jest/valid-expect-in-promise", + "jest/valid-title": "jest/valid-title", + "jest-dom/prefer-checked": "jest-dom/prefer-checked", + "jest-dom/prefer-empty": "jest-dom/prefer-empty", + "jest-dom/prefer-enabled-disabled": "jest-dom/prefer-enabled-disabled", + "jest-dom/prefer-focus": "jest-dom/prefer-focus", + "jest-dom/prefer-in-document": "jest-dom/prefer-in-document", + "jest-dom/prefer-required": "jest-dom/prefer-required", + "jest-dom/prefer-to-have-attribute": "jest-dom/prefer-to-have-attribute", + "jest-dom/prefer-to-have-class": "jest-dom/prefer-to-have-class", + "jest-dom/prefer-to-have-style": "jest-dom/prefer-to-have-style", + "jest-dom/prefer-to-have-text-content": "jest-dom/prefer-to-have-text-content", + "jest-dom/prefer-to-have-value": "jest-dom/prefer-to-have-value", + "testing-library/await-async-events": "testing-library/await-async-events", + "testing-library/await-async-queries": "testing-library/await-async-queries", + "testing-library/await-async-utils": "testing-library/await-async-utils", + "testing-library/no-await-sync-events": "testing-library/no-await-sync-events", + "testing-library/no-await-sync-queries": "testing-library/no-await-sync-queries", + "testing-library/no-container": "testing-library/no-container", + "testing-library/no-debugging-utils": "testing-library/no-debugging-utils", + "testing-library/no-dom-import": "testing-library/no-dom-import", + "testing-library/no-global-regexp-flag-in-query": "testing-library/no-global-regexp-flag-in-query", + "testing-library/no-manual-cleanup": "testing-library/no-manual-cleanup", + "testing-library/no-node-access": "testing-library/no-node-access", + "testing-library/no-promise-in-fire-event": "testing-library/no-promise-in-fire-event", + "testing-library/no-render-in-lifecycle": "testing-library/no-render-in-lifecycle", + "testing-library/no-unnecessary-act": "testing-library/no-unnecessary-act", + "testing-library/no-wait-for-multiple-assertions": "testing-library/no-wait-for-multiple-assertions", + "testing-library/no-wait-for-side-effects": "testing-library/no-wait-for-side-effects", + "testing-library/no-wait-for-snapshot": "testing-library/no-wait-for-snapshot", + "testing-library/prefer-find-by": "testing-library/prefer-find-by", + "testing-library/prefer-presence-queries": "testing-library/prefer-presence-queries", + "testing-library/prefer-query-by-disappearance": "testing-library/prefer-query-by-disappearance", + "testing-library/prefer-screen-queries": "testing-library/prefer-screen-queries", + "testing-library/render-result-naming-convention": "testing-library/render-result-naming-convention", + "prefer-spread": "prefer-spread", + "@typescript-eslint/ban-ts-comment": "typescript/ban-ts-comment", + "@typescript-eslint/no-array-constructor": "typescript-js/no-array-constructor", + "@typescript-eslint/no-duplicate-enum-values": "typescript/no-duplicate-enum-values", + "@typescript-eslint/no-empty-object-type": "typescript-js/no-empty-object-type", + "@typescript-eslint/no-extra-non-null-assertion": "typescript/no-extra-non-null-assertion", + "@typescript-eslint/no-misused-new": "typescript/no-misused-new", + "@typescript-eslint/no-non-null-asserted-optional-chain": "typescript/no-non-null-asserted-optional-chain", + "@typescript-eslint/no-require-imports": "typescript/no-require-imports", + "@typescript-eslint/no-unnecessary-type-constraint": "typescript/no-unnecessary-type-constraint", + "@typescript-eslint/no-unsafe-function-type": "typescript/no-unsafe-function-type", + "@typescript-eslint/no-unused-vars": "no-unused-vars", + "@typescript-eslint/no-wrapper-object-types": "typescript/no-wrapper-object-types", + "@typescript-eslint/prefer-as-const": "typescript/prefer-as-const", + "@typescript-eslint/prefer-namespace-keyword": "typescript/prefer-namespace-keyword", + "@typescript-eslint/triple-slash-reference": "typescript/triple-slash-reference", + "metabase/no-unjustified-type-casts": "metabase/no-unjustified-type-casts", + "@typescript-eslint/consistent-type-imports": "typescript/consistent-type-imports", + "@typescript-eslint/no-import-type-side-effects": "typescript/no-import-type-side-effects", + "no-console": "no-console", + "@typescript-eslint/no-restricted-imports": "typescript-js/no-restricted-imports", + "ttag/no-module-declaration": "ttag/no-module-declaration", + "import/no-commonjs": "import/no-commonjs", + "metabase/no-unconditional-metabase-links-render": "metabase/no-unconditional-metabase-links-render", + "metabase/no-color-literals": "metabase/no-color-literals", + "metabase/no-literal-metabase-strings": "metabase/no-literal-metabase-strings", + "i18next/no-literal-string": "i18next/no-literal-string", + "boundaries/element-types": "boundaries/element-types", + "metabase/enforce-module-public-api": "metabase/enforce-module-public-api", + "boundaries/no-unknown-files": "boundaries/no-unknown-files", + "@typescript-eslint/no-namespace": "typescript/no-namespace", + "@typescript-eslint/no-unused-expressions": "no-unused-expressions", + "metabase/no-base-api-access": "metabase/no-base-api-access", + "metabase/no-plugin-slot-outside-plugins-files": "metabase/no-plugin-slot-outside-plugins-files", + "storybook/await-interactions": "storybook/await-interactions", + "storybook/context-in-play-function": "storybook/context-in-play-function", + "storybook/default-exports": "storybook/default-exports", + "storybook/hierarchy-separator": "storybook/hierarchy-separator", + "storybook/no-redundant-story-name": "storybook/no-redundant-story-name", + "storybook/prefer-pascal-case": "storybook/prefer-pascal-case", + "storybook/story-exports": "storybook/story-exports", + "storybook/use-storybook-expect": "storybook/use-storybook-expect", + "storybook/use-storybook-testing-library": "storybook/use-storybook-testing-library", + "metabase/no-external-references-for-sdk-package-code": "metabase/no-external-references-for-sdk-package-code", + "import/no-cycle": "import/no-cycle", + "metabase/no-module-side-effects": "metabase/no-module-side-effects", + "prefer-rest-params": "prefer-rest-params", + "@typescript-eslint/no-explicit-any": "typescript/no-explicit-any", + "@typescript-eslint/no-this-alias": "typescript/no-this-alias", + "@typescript-eslint/no-unsafe-declaration-merging": "typescript/no-unsafe-declaration-merging", + "storybook/no-renderer-packages": "storybook/no-renderer-packages", + "storybook/no-uninstalled-addons": "storybook/no-uninstalled-addons" +} diff --git a/frontend/lint/recommended-rules.json b/frontend/lint/recommended-rules.json new file mode 100644 index 00000000000..5f83527a8b1 --- /dev/null +++ b/frontend/lint/recommended-rules.json @@ -0,0 +1,280 @@ +{ + "javascript": { + "constructor-super": "error", + "for-direction": "error", + "getter-return": "error", + "no-async-promise-executor": "error", + "no-case-declarations": "error", + "no-class-assign": "error", + "no-compare-neg-zero": "error", + "no-cond-assign": "error", + "no-const-assign": "error", + "no-constant-binary-expression": "error", + "no-constant-condition": "error", + "no-control-regex": "error", + "no-debugger": "error", + "no-delete-var": "error", + "no-dupe-args": "error", + "no-dupe-class-members": "error", + "no-dupe-else-if": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-empty": "error", + "no-empty-character-class": "error", + "no-empty-pattern": "error", + "no-empty-static-block": "error", + "no-ex-assign": "error", + "no-extra-boolean-cast": "error", + "no-fallthrough": "error", + "no-func-assign": "error", + "no-global-assign": "error", + "no-import-assign": "error", + "no-invalid-regexp": "error", + "no-irregular-whitespace": "error", + "no-loss-of-precision": "error", + "no-misleading-character-class": "error", + "no-new-native-nonconstructor": "error", + "no-nonoctal-decimal-escape": "error", + "no-obj-calls": "error", + "no-octal": "error", + "no-prototype-builtins": "error", + "no-redeclare": "error", + "no-regex-spaces": "error", + "no-self-assign": "error", + "no-setter-return": "error", + "no-shadow-restricted-names": "error", + "no-sparse-arrays": "error", + "no-this-before-super": "error", + "no-undef": "error", + "no-unexpected-multiline": "error", + "no-unreachable": "error", + "no-unsafe-finally": "error", + "no-unsafe-negation": "error", + "no-unsafe-optional-chaining": "error", + "no-unused-labels": "error", + "no-unused-private-class-members": "error", + "no-unused-vars": "error", + "no-useless-backreference": "error", + "no-useless-catch": "error", + "no-useless-escape": "error", + "no-with": "error", + "require-yield": "error", + "use-isnan": "error", + "valid-typeof": "error" + }, + "react": { + "react/display-name": 2, + "react/jsx-key": 2, + "react/jsx-no-comment-textnodes": 2, + "react/jsx-no-duplicate-props": 2, + "react/jsx-no-target-blank": 2, + "react/jsx-no-undef": 2, + "react/jsx-uses-react": 2, + "react/jsx-uses-vars": 2, + "react/no-children-prop": 2, + "react/no-danger-with-children": 2, + "react/no-deprecated": 2, + "react/no-direct-mutation-state": 2, + "react/no-find-dom-node": 2, + "react/no-is-mounted": 2, + "react/no-render-return-value": 2, + "react/no-string-refs": 2, + "react/no-unescaped-entities": 2, + "react/no-unknown-property": 2, + "react/no-unsafe": 0, + "react/prop-types": 2, + "react/react-in-jsx-scope": 2, + "react/require-render-return": 2 + }, + "jsxRuntime": { + "react/react-in-jsx-scope": 0, + "react/jsx-uses-react": 0 + }, + "hooks": { + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn" + }, + "i18next": { + "i18next/no-literal-string": [ + 2 + ] + }, + "typescript": [ + { + "name": "typescript-eslint/base", + "languageOptions": { + "sourceType": "module" + } + }, + { + "files": [ + "**/*.ts", + "**/*.tsx", + "**/*.mts", + "**/*.cts" + ], + "rules": { + "constructor-super": "off", + "getter-return": "off", + "no-class-assign": "off", + "no-const-assign": "off", + "no-dupe-args": "off", + "no-dupe-class-members": "off", + "no-dupe-keys": "off", + "no-func-assign": "off", + "no-import-assign": "off", + "no-new-native-nonconstructor": "off", + "no-new-symbol": "off", + "no-obj-calls": "off", + "no-redeclare": "off", + "no-setter-return": "off", + "no-this-before-super": "off", + "no-undef": "off", + "no-unreachable": "off", + "no-unsafe-negation": "off", + "no-var": "error", + "no-with": "off", + "prefer-const": "error", + "prefer-rest-params": "error", + "prefer-spread": "error" + }, + "name": "typescript-eslint/eslint-recommended" + }, + { + "name": "typescript-eslint/recommended", + "rules": { + "@typescript-eslint/ban-ts-comment": "error", + "no-array-constructor": "off", + "@typescript-eslint/no-array-constructor": "error", + "@typescript-eslint/no-duplicate-enum-values": "error", + "@typescript-eslint/no-empty-object-type": "error", + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-extra-non-null-assertion": "error", + "@typescript-eslint/no-misused-new": "error", + "@typescript-eslint/no-namespace": "error", + "@typescript-eslint/no-non-null-asserted-optional-chain": "error", + "@typescript-eslint/no-require-imports": "error", + "@typescript-eslint/no-this-alias": "error", + "@typescript-eslint/no-unnecessary-type-constraint": "error", + "@typescript-eslint/no-unsafe-declaration-merging": "error", + "@typescript-eslint/no-unsafe-function-type": "error", + "no-unused-expressions": "off", + "@typescript-eslint/no-unused-expressions": "error", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-wrapper-object-types": "error", + "@typescript-eslint/prefer-as-const": "error", + "@typescript-eslint/prefer-namespace-keyword": "error", + "@typescript-eslint/triple-slash-reference": "error" + } + } + ], + "jest": { + "jest/expect-expect": "warn", + "jest/no-alias-methods": "error", + "jest/no-commented-out-tests": "warn", + "jest/no-conditional-expect": "error", + "jest/no-deprecated-functions": "error", + "jest/no-disabled-tests": "warn", + "jest/no-done-callback": "error", + "jest/no-export": "error", + "jest/no-focused-tests": "error", + "jest/no-identical-title": "error", + "jest/no-interpolation-in-snapshots": "error", + "jest/no-jasmine-globals": "error", + "jest/no-mocks-import": "error", + "jest/no-standalone-expect": "error", + "jest/no-test-prefixes": "error", + "jest/valid-describe-callback": "error", + "jest/valid-expect": "error", + "jest/valid-expect-in-promise": "error", + "jest/valid-title": "error" + }, + "jestDom": { + "jest-dom/prefer-checked": "error", + "jest-dom/prefer-empty": "error", + "jest-dom/prefer-enabled-disabled": "error", + "jest-dom/prefer-focus": "error", + "jest-dom/prefer-in-document": "error", + "jest-dom/prefer-required": "error", + "jest-dom/prefer-to-have-attribute": "error", + "jest-dom/prefer-to-have-class": "error", + "jest-dom/prefer-to-have-style": "error", + "jest-dom/prefer-to-have-text-content": "error", + "jest-dom/prefer-to-have-value": "error" + }, + "testingLibrary": { + "testing-library/await-async-events": [ + "error", + { + "eventModule": "userEvent" + } + ], + "testing-library/await-async-queries": "error", + "testing-library/await-async-utils": "error", + "testing-library/no-await-sync-events": [ + "error", + { + "eventModules": [ + "fire-event" + ] + } + ], + "testing-library/no-await-sync-queries": "error", + "testing-library/no-container": "error", + "testing-library/no-debugging-utils": "warn", + "testing-library/no-dom-import": [ + "error", + "react" + ], + "testing-library/no-global-regexp-flag-in-query": "error", + "testing-library/no-manual-cleanup": "error", + "testing-library/no-node-access": "error", + "testing-library/no-promise-in-fire-event": "error", + "testing-library/no-render-in-lifecycle": "error", + "testing-library/no-unnecessary-act": "error", + "testing-library/no-wait-for-multiple-assertions": "error", + "testing-library/no-wait-for-side-effects": "error", + "testing-library/no-wait-for-snapshot": "error", + "testing-library/prefer-find-by": "error", + "testing-library/prefer-presence-queries": "error", + "testing-library/prefer-query-by-disappearance": "error", + "testing-library/prefer-screen-queries": "error", + "testing-library/render-result-naming-convention": "error" + }, + "storybook": [ + { + "name": "storybook:recommended:setup" + }, + { + "name": "storybook:recommended:stories-rules", + "files": [ + "**/*.stories.@(ts|tsx|js|jsx|mjs|cjs)", + "**/*.story.@(ts|tsx|js|jsx|mjs|cjs)" + ], + "rules": { + "react-hooks/rules-of-hooks": "off", + "import/no-anonymous-default-export": "off", + "storybook/await-interactions": "error", + "storybook/context-in-play-function": "error", + "storybook/default-exports": "error", + "storybook/hierarchy-separator": "warn", + "storybook/no-redundant-story-name": "warn", + "storybook/no-renderer-packages": "error", + "storybook/prefer-pascal-case": "warn", + "storybook/story-exports": "error", + "storybook/use-storybook-expect": "error", + "storybook/use-storybook-testing-library": "error" + } + }, + { + "name": "storybook:recommended:main-rules", + "files": [ + ".storybook/main.@(js|cjs|mjs|ts)" + ], + "rules": { + "storybook/no-uninstalled-addons": "error" + } + } + ] +} diff --git a/frontend/lint/side-effect-registry.js b/frontend/lint/side-effect-registry.js index b1b70f85de8..a5c23907fd2 100644 --- a/frontend/lint/side-effect-registry.js +++ b/frontend/lint/side-effect-registry.js @@ -25,10 +25,19 @@ function loadRegistry(registryPath = DEFAULT_REGISTRY_PATH) { }; } +const patternMatchers = new WeakMap(); function matchingPatterns(registry, file) { - return registry.patterns.filter((pattern) => - micromatch.isMatch(file, pattern), - ); + let matchers = patternMatchers.get(registry); + if (!matchers) { + matchers = registry.patterns.map((pattern) => ({ + pattern, + isMatch: micromatch.matcher(pattern), + })); + patternMatchers.set(registry, matchers); + } + return matchers + .filter(({ isMatch }) => isMatch(file)) + .map(({ pattern }) => pattern); } // An unclassified file counts as "global", and an exact key wins over a pattern. diff --git a/frontend/lint/tests/enforce-module-public-api.unit.spec.js b/frontend/lint/tests/enforce-module-public-api.unit.spec.js index f4fa8a7f8cc..4219f8b05ba 100644 --- a/frontend/lint/tests/enforce-module-public-api.unit.spec.js +++ b/frontend/lint/tests/enforce-module-public-api.unit.spec.js @@ -1,11 +1,9 @@ -import { RuleTester } from "eslint"; - import rule from "../eslint-plugin-metabase/rules/enforce-module-public-api"; import { getPublicApiModules } from "../module-boundaries.mjs"; -const ruleTester = new RuleTester({ - languageOptions: { ecmaVersion: 2022, sourceType: "module" }, -}); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); const options = [{ modules: ["metabase/analytics"] }]; diff --git a/frontend/lint/tests/fixtures/resolve-config-parity.js b/frontend/lint/tests/fixtures/resolve-config-parity.js new file mode 100644 index 00000000000..4c038f3ff6f --- /dev/null +++ b/frontend/lint/tests/fixtures/resolve-config-parity.js @@ -0,0 +1,62 @@ +const assert = require("node:assert/strict"); +const path = require("node:path"); + +const sdk = require("../../../build/embedding-sdk/rspack/resolve-config"); +const { + ENTERPRISE_SRC_PATH, +} = require("../../../build/shared/rspack/resolve-aliases"); +const app = require("../../../build/shared/rspack/resolve-config"); + +// Snapshot what lint sees before full configuration loading can mutate shared objects. +const light = { app: structuredClone(app), sdk: structuredClone(sdk) }; + +const fromNodeModules = Object.keys(require.cache).find((file) => + file.includes(`${path.sep}node_modules${path.sep}`), +); +assert.equal( + fromNodeModules, + undefined, + `lint's resolve configs must stay dependency free, but loaded ${fromNodeModules}`, +); + +const SDK_ENTERPRISE_ALIASES = { + "sdk-ee-plugins": "/sdk-plugins", + "sdk-iframe-embedding-ee-plugins": "/sdk-iframe-embedding-plugins", + "ee-overrides": "/overrides", +}; + +for (const [alias, subpath] of Object.entries(SDK_ENTERPRISE_ALIASES)) { + assert.equal( + light.sdk.resolve.alias[alias], + ENTERPRISE_SRC_PATH + subpath, + `${alias}: the SDK resolves to the enterprise tree in every edition`, + ); + if (process.env.MB_EDITION === "ee") { + assert.equal( + light.app.resolve.alias[alias], + light.sdk.resolve.alias[alias], + `${alias}: EE app`, + ); + } else { + assert.ok( + !light.app.resolve.alias[alias].startsWith(ENTERPRISE_SRC_PATH), + `${alias}: the OSS app must resolve to a noop stub, got ${light.app.resolve.alias[alias]}`, + ); + } +} + +for (const { name, snapshot, full } of [ + { + name: "app", + snapshot: light.app, + full: require("../../../../rspack.main.config"), + }, + { + name: "sdk", + snapshot: light.sdk, + full: require("../../../../rspack.embedding-sdk-bundle.config"), + }, +]) { + assert.deepEqual(snapshot.resolve, full.resolve, `${name}: resolve`); + assert.deepEqual(snapshot.externals, full.externals, `${name}: externals`); +} diff --git a/frontend/lint/tests/jtag-missing-key.unit.spec.js b/frontend/lint/tests/jtag-missing-key.unit.spec.js index 532ce73c05a..3d8e8d4b0cb 100644 --- a/frontend/lint/tests/jtag-missing-key.unit.spec.js +++ b/frontend/lint/tests/jtag-missing-key.unit.spec.js @@ -1,16 +1,8 @@ -import { RuleTester } from "eslint"; - import rule from "../eslint-plugin-metabase/rules/jtag-missing-key"; -const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 2015, - sourceType: "module", - parserOptions: { - ecmaFeatures: { jsx: true }, - }, - }, -}); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); const VALID_CASES = [ { diff --git a/frontend/lint/tests/no-analytics-import-outside-analytics-files.unit.spec.js b/frontend/lint/tests/no-analytics-import-outside-analytics-files.unit.spec.js index 2a259adad5b..3515d35b661 100644 --- a/frontend/lint/tests/no-analytics-import-outside-analytics-files.unit.spec.js +++ b/frontend/lint/tests/no-analytics-import-outside-analytics-files.unit.spec.js @@ -1,14 +1,8 @@ -import { RuleTester } from "eslint"; -import tseslint from "typescript-eslint"; - import rule from "../eslint-plugin-metabase/rules/no-analytics-import-outside-analytics-files"; -const ruleTester = new RuleTester({ - languageOptions: { - parser: tseslint.parser, - sourceType: "module", - }, -}); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); const errorMessage = /is only allowed in files named "analytics" or inside an "analytics\/" directory/; diff --git a/frontend/lint/tests/no-base-api-access.unit.spec.js b/frontend/lint/tests/no-base-api-access.unit.spec.js index 40a646673dc..ec8e3c1c303 100644 --- a/frontend/lint/tests/no-base-api-access.unit.spec.js +++ b/frontend/lint/tests/no-base-api-access.unit.spec.js @@ -1,15 +1,8 @@ -import { RuleTester } from "eslint"; -import tseslint from "typescript-eslint"; - import rule from "../eslint-plugin-metabase/rules/no-base-api-access"; -const ruleTester = new RuleTester({ - languageOptions: { - parser: tseslint.parser, - parserOptions: { ecmaFeatures: { jsx: true } }, - sourceType: "module", - }, -}); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); const COMPONENT_FILE = "/repo/frontend/src/metabase/foo/components/Foo.tsx"; const OWNER_FILE = "/repo/frontend/src/metabase/foo/api/bar.ts"; diff --git a/frontend/lint/tests/no-direct-memoize-import.unit.spec.js b/frontend/lint/tests/no-direct-memoize-import.unit.spec.js index 09d0912b4d1..25c8e2eca1c 100644 --- a/frontend/lint/tests/no-direct-memoize-import.unit.spec.js +++ b/frontend/lint/tests/no-direct-memoize-import.unit.spec.js @@ -1,10 +1,8 @@ -import { RuleTester } from "eslint"; - import rule from "../eslint-plugin-metabase/rules/no-direct-memoize-import"; -const ruleTester = new RuleTester({ - languageOptions: { ecmaVersion: 2022, sourceType: "module" }, -}); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); const error = { messageId: "noDirectMemoizeImport" }; const FILE = "/repo/frontend/src/metabase/thing/new-file.ts"; diff --git a/frontend/lint/tests/no-literal-metabase-strings.unit.spec.js b/frontend/lint/tests/no-literal-metabase-strings.unit.spec.js index a543ddb9b38..ddccf527218 100644 --- a/frontend/lint/tests/no-literal-metabase-strings.unit.spec.js +++ b/frontend/lint/tests/no-literal-metabase-strings.unit.spec.js @@ -1,16 +1,8 @@ -import { RuleTester } from "eslint"; - import rule from "../eslint-plugin-metabase/rules/no-literal-metabase-strings"; -const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 2015, - sourceType: "module", - parserOptions: { - ecmaFeatures: { jsx: true }, - }, - }, -}); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); const VALID_CASES = [ { diff --git a/frontend/lint/tests/no-locale-with-intl-functions.unit.spec.js b/frontend/lint/tests/no-locale-with-intl-functions.unit.spec.js index 34901832d92..b74c27e04ec 100644 --- a/frontend/lint/tests/no-locale-with-intl-functions.unit.spec.js +++ b/frontend/lint/tests/no-locale-with-intl-functions.unit.spec.js @@ -1,16 +1,8 @@ -import { RuleTester } from "eslint"; - import rule from "../eslint-plugin-metabase/rules/no-locale-with-intl-functions"; -const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 2015, - sourceType: "module", - parserOptions: { - ecmaFeatures: { jsx: true }, - }, - }, -}); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); const VALID_CASES = [ { diff --git a/frontend/lint/tests/no-module-level-memoize.unit.spec.js b/frontend/lint/tests/no-module-level-memoize.unit.spec.js index 059efd083ee..7c77020a27e 100644 --- a/frontend/lint/tests/no-module-level-memoize.unit.spec.js +++ b/frontend/lint/tests/no-module-level-memoize.unit.spec.js @@ -1,10 +1,8 @@ -import { RuleTester } from "eslint"; - import rule from "../eslint-plugin-metabase/rules/no-module-level-memoize"; -const ruleTester = new RuleTester({ - languageOptions: { ecmaVersion: 2022, sourceType: "module" }, -}); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); const error = { messageId: "noModuleLevelMemoize" }; const FILE = "/repo/frontend/src/metabase/thing/new-file.ts"; diff --git a/frontend/lint/tests/no-module-side-effects.unit.spec.js b/frontend/lint/tests/no-module-side-effects.unit.spec.js index 6257c68e32c..d4bd6a561f2 100644 --- a/frontend/lint/tests/no-module-side-effects.unit.spec.js +++ b/frontend/lint/tests/no-module-side-effects.unit.spec.js @@ -1,17 +1,10 @@ import path from "path"; -import { RuleTester } from "eslint"; -import tseslint from "typescript-eslint"; - import rule from "../eslint-plugin-metabase/rules/no-module-side-effects"; -const ruleTester = new RuleTester({ - languageOptions: { - parser: tseslint.parser, - parserOptions: { ecmaFeatures: { jsx: true } }, - sourceType: "module", - }, -}); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); const FILENAME = "/repo/frontend/src/metabase/widgets/Widget/Widget.tsx"; const REGISTRATION_FILE = @@ -21,10 +14,8 @@ const REGISTRATION_DIR = "/repo/frontend/src/metabase/widgets/api/"; const options = [{ sideEffectPaths: [REGISTRATION_FILE, REGISTRATION_DIR] }]; -// Real files, because the import check resolves each import on disk before looking it up. -// registry.json classifies effects/{global,entry,self,registration}.ts, leaves unclassified.ts -// unclassified, names facade/ a facade, and lists the packages leaflet-draw and @mantine/core/styles.css. -// pure.ts is not listed. +// The import checks resolve each specifier on disk before consulting the registry, +// so these cases point at real fixture files. const FIXTURES = path.resolve(__dirname, "fixtures/side-effect-files"); const IMPORTER = path.join(FIXTURES, "importer/Widget.tsx"); const registryOptions = { diff --git a/frontend/lint/tests/no-plugin-slot-outside-plugins-files.unit.spec.js b/frontend/lint/tests/no-plugin-slot-outside-plugins-files.unit.spec.js index bffc9fe456d..56b36becb94 100644 --- a/frontend/lint/tests/no-plugin-slot-outside-plugins-files.unit.spec.js +++ b/frontend/lint/tests/no-plugin-slot-outside-plugins-files.unit.spec.js @@ -1,14 +1,8 @@ -import { RuleTester } from "eslint"; -import tseslint from "typescript-eslint"; - import rule from "../eslint-plugin-metabase/rules/no-plugin-slot-outside-plugins-files"; -const ruleTester = new RuleTester({ - languageOptions: { - parser: tseslint.parser, - sourceType: "module", - }, -}); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); const MODULE_FILE = "/repo/frontend/src/metabase/search/slots.ts"; const MODULE_INDEX = "/repo/frontend/src/metabase/search/index.ts"; diff --git a/frontend/lint/tests/no-unconditional-metabase-links-render.unit.spec.js b/frontend/lint/tests/no-unconditional-metabase-links-render.unit.spec.js index 5187104e9dd..313ea5be632 100644 --- a/frontend/lint/tests/no-unconditional-metabase-links-render.unit.spec.js +++ b/frontend/lint/tests/no-unconditional-metabase-links-render.unit.spec.js @@ -1,16 +1,8 @@ -import { RuleTester } from "eslint"; - import rule from "../eslint-plugin-metabase/rules/no-unconditional-metabase-links-render"; -const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 2015, - sourceType: "module", - parserOptions: { - ecmaFeatures: { jsx: true }, - }, - }, -}); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); // Only importing `getShowMetabaseLinks` is not enough to pass the rule. // This doesn't necessarily mean that the link would be definitely conditionally diff --git a/frontend/lint/tests/no-unjustified-type-casts.unit.spec.js b/frontend/lint/tests/no-unjustified-type-casts.unit.spec.js index abf74f4934b..ae4d394eaaa 100644 --- a/frontend/lint/tests/no-unjustified-type-casts.unit.spec.js +++ b/frontend/lint/tests/no-unjustified-type-casts.unit.spec.js @@ -1,17 +1,11 @@ -import { RuleTester } from "eslint"; -import tseslint from "typescript-eslint"; - import rule from "../eslint-plugin-metabase/rules/no-unjustified-type-casts"; +import { createRuleTester } from "./rule-tester"; + const TS_FILENAME = "spec.ts"; // Angle-bracket casts only parse with JSX off const TSX_FILENAME = "spec.tsx"; -const ruleTester = new RuleTester({ - languageOptions: { - parser: tseslint.parser, - sourceType: "module", - }, -}); +const ruleTester = createRuleTester(); const message = /Type casts .* must be preceded by a comment/; diff --git a/frontend/lint/tests/no-unordered-test-helpers.unit.spec.js b/frontend/lint/tests/no-unordered-test-helpers.unit.spec.js index ca412dbbcba..685a249faba 100644 --- a/frontend/lint/tests/no-unordered-test-helpers.unit.spec.js +++ b/frontend/lint/tests/no-unordered-test-helpers.unit.spec.js @@ -1,8 +1,8 @@ -import { RuleTester } from "eslint"; - import rule from "../eslint-plugin-metabase/rules/no-unordered-test-helpers"; -const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2015 } }); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); const orderError = { message: "H.restore() must come before H.resetTestTable()", diff --git a/frontend/lint/tests/no-unsafe-element-filtering.unit.spec.js b/frontend/lint/tests/no-unsafe-element-filtering.unit.spec.js index 38aeaed1726..159a62c883f 100644 --- a/frontend/lint/tests/no-unsafe-element-filtering.unit.spec.js +++ b/frontend/lint/tests/no-unsafe-element-filtering.unit.spec.js @@ -1,8 +1,8 @@ -import { RuleTester } from "eslint"; - import rule from "../eslint-plugin-metabase/rules/no-unsafe-element-filtering"; -const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2015 } }); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); const unsafeError = { messageId: "unexpected", diff --git a/frontend/lint/tests/no-unscoped-text-selectors.unit.spec.js b/frontend/lint/tests/no-unscoped-text-selectors.unit.spec.js index d0adbc1a35c..494e2bd5359 100644 --- a/frontend/lint/tests/no-unscoped-text-selectors.unit.spec.js +++ b/frontend/lint/tests/no-unscoped-text-selectors.unit.spec.js @@ -1,8 +1,8 @@ -import { RuleTester } from "eslint"; - import rule from "../eslint-plugin-metabase/rules/no-unscoped-text-selectors"; -const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2015 } }); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); const scopeError = { message: /should be scoped/i }; diff --git a/frontend/lint/tests/oxlint-boundaries.test.mjs b/frontend/lint/tests/oxlint-boundaries.test.mjs new file mode 100644 index 00000000000..c686c647812 --- /dev/null +++ b/frontend/lint/tests/oxlint-boundaries.test.mjs @@ -0,0 +1,236 @@ +import assert from "node:assert/strict"; +import { createRequire } from "node:module"; +import path from "node:path"; +import test from "node:test"; + +import { ESLint } from "eslint"; + +import { + elements, + enforcedRules, + boundaryOptions, + boundarySettings, +} from "../module-boundaries.mjs"; +import { + createBoundaryChecker, + createBoundaryPlugin, +} from "../oxlint-boundaries.mjs"; +import { ruleAliases } from "../oxlint/rule-aliases.mjs"; + +const root = path.resolve(import.meta.dirname, "../../.."); + +const require = createRequire(import.meta.url); +const checker = createBoundaryChecker({ + elements, + rules: enforcedRules, + rootPath: root, +}); + +test("should accept aliased suppressions in the standalone boundary command", async () => { + const eslint = new ESLint({ + cwd: root, + overrideConfigFile: path.join(root, "eslint.config.module-boundaries.mjs"), + }); + const suppression = `/* eslint-disable ${Object.values(ruleAliases).join(", ")} */`; + const [valid] = await eslint.lintText(`${suppression}\nconst unused = 1;`, { + filePath: "frontend/src/metabase/dayjs/index.ts", + }); + assert.deepEqual(valid.messages, []); + + const [invalid] = await eslint.lintText( + `${suppression}\nimport "metabase/ui";`, + { filePath: "frontend/src/metabase/dayjs/index.ts" }, + ); + assert.deepEqual( + invalid.messages.map((message) => message.ruleId), + ["boundaries/element-types"], + ); +}); + +test("should classify full paths before folders, including dot directories", () => { + const expectedTypes = { + "frontend/src/metabase/static-viz/index.tsx": "app/misc", + "frontend/src/metabase/static-viz/other.tsx": "shared/static-viz", + "frontend/src/metabase-lib/v1/metadata/Field.ts": "basic/mlv1", + "frontend/src/metabase-lib/query.ts": "lib/mlv2", + "frontend/src/embedding-sdk-shared/.storybook/preview.tsx": "app/misc", + "frontend/src/metabase/env.ts": "lib/env", + "frontend/src/metabase/unknown-module/file.ts": null, + "frontend/src/metabase/dayjs/.hidden/file.ts": null, + }; + for (const [file, type] of Object.entries(expectedTypes)) { + assert.equal(checker.classify(path.join(root, file)).type, type, file); + } + assert.equal( + checker.classify(path.join(root, "e2e/fixture.ts")).isIgnored, + true, + ); +}); + +test("should match upstream decisions for every declared module pair", () => { + const pluginRoot = path.dirname(require.resolve("eslint-plugin-boundaries")); + const { getSettings } = require( + path.join(pluginRoot, "Settings/Validations.js"), + ); + const { elementRulesAllowDependency } = require( + path.join(pluginRoot, "Rules/ElementTypes.js"), + ); + const settings = getSettings({ + settings: { ...boundarySettings, "boundaries/root-path": root }, + }); + assert.deepEqual(checker.types, [ + ...new Set(elements.map((element) => element.type)), + ]); + const element = (type) => ({ + type, + origin: "local", + category: null, + captured: {}, + path: `${type}/file.ts`, + elementPath: type, + internalPath: "file.ts", + parents: [], + isIgnored: false, + isUnknown: false, + }); + for (const from of checker.types) { + for (const to of checker.types) { + const dependency = { + from: element(from), + to: { ...element(to), source: to }, + dependency: { + kind: "value", + nodeKind: "import", + relationship: { from: null, to: null }, + specifiers: [], + }, + }; + const expected = elementRulesAllowDependency( + dependency, + settings, + boundaryOptions, + ); + assert.equal( + checker.decision(from, to).allowed, + expected.result, + `${from} -> ${to}`, + ); + } + } +}); + +test("should enforce shared-tier restrictions and exceptions", () => { + for (const { from, to, allowed } of [ + { + from: "shared/current-user", + to: "shared/metadata-store", + allowed: false, + }, + { from: "shared/metadata-store", to: "shared/current-user", allowed: true }, + { from: "shared/nav", to: "shared/palette", allowed: true }, + { from: "shared/palette", to: "shared/nav", allowed: true }, + { from: "lib/dayjs", to: "feature/query_builder", allowed: false }, + ]) { + assert.equal( + checker.decision(from, to).allowed, + allowed, + `${from} -> ${to}`, + ); + } + for (const type of checker.types) { + assert.equal(checker.decision(type, type).allowed, true, type); + } +}); + +function elementTypes(from, to) { + const reports = []; + const plugin = createBoundaryPlugin({ resolve: () => path.join(root, to) }); + const visitor = plugin.rules["element-types"].create({ + filename: path.join(root, from), + options: [], + report: (report) => reports.push(report), + }); + return { visitor, reports }; +} + +test("should check static and dynamic imports with string sources", () => { + const from = "frontend/src/metabase/dayjs/index.ts"; + const to = "frontend/src/metabase/query_builder/index.ts"; + const message = "lib/dayjs cannot import from feature/query_builder"; + const source = { type: "Literal", value: "metabase/query_builder" }; + const attributes = { type: "ObjectExpression", properties: [] }; + const stringAttributes = { + type: "Literal", + value: "metabase/query_builder/attributes", + }; + const template = { type: "TemplateLiteral", quasis: [], expressions: [] }; + + for (const { name, visit, reported } of [ + { + name: "static import", + visit: (visitor) => visitor.ImportDeclaration({ source }), + reported: [source], + }, + { + name: "dynamic import", + visit: (visitor) => visitor.ImportExpression({ source, options: null }), + reported: [source], + }, + { + name: "dynamic import with attributes", + visit: (visitor) => + visitor.ImportExpression({ source, options: attributes }), + reported: [source], + }, + { + name: "dynamic import with a string second argument", + visit: (visitor) => + visitor.ImportExpression({ source, options: stringAttributes }), + reported: [source, stringAttributes], + }, + { + name: "template literal source", + visit: (visitor) => + visitor.ImportExpression({ source: template, options: null }), + reported: [], + }, + ]) { + const { visitor, reports } = elementTypes(from, to); + visit(visitor); + assert.deepEqual( + reports, + reported.map((node) => ({ node, message })), + name, + ); + } + + const allowed = elementTypes(to, from); + allowed.visitor.ImportDeclaration({ + source: { type: "Literal", value: "metabase/dayjs" }, + }); + assert.deepEqual(allowed.reports, []); +}); + +test("should reject unsupported boundary policies", () => { + for (const { input, error } of [ + { + input: { + elements, + rules: [{ from: ["lib/*"], allow: ["lib/*"], importKind: "type" }], + }, + error: /Unsupported boundary policy/, + }, + { + input: { + elements: [{ type: "lib/a", pattern: "a/*", capture: ["name"] }], + rules: [], + }, + error: /Unsupported boundary descriptor/, + }, + ]) { + assert.throws( + () => createBoundaryChecker({ ...input, rootPath: root }), + error, + ); + } +}); diff --git a/frontend/lint/tests/oxlint-compatibility.test.mjs b/frontend/lint/tests/oxlint-compatibility.test.mjs new file mode 100644 index 00000000000..fc8ff8728e8 --- /dev/null +++ b/frontend/lint/tests/oxlint-compatibility.test.mjs @@ -0,0 +1,441 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import test from "node:test"; + +import { ESLint } from "eslint"; +import tseslint from "typescript-eslint"; + +import { createConfig, root } from "../oxlint/config.mjs"; +import { decodeJsxText } from "../oxlint/jsx-text.mjs"; +import ruleMap from "../oxlint/rule-map.json" with { type: "json" }; + +const binary = path.join(root, "node_modules/oxlint/bin/oxlint"); + +test("should load every mapped rule", () => { + const config = createConfig(); + const loaded = new Set([ + "eslint", + ...config.plugins, + ...config.jsPlugins.map((p) => p.name), + ]); + for (const name of Object.values(ruleMap)) { + const namespace = name.includes("/") + ? name.slice(0, name.indexOf("/")) + : "eslint"; + assert.ok(loaded.has(namespace), `${name}: plugin is not loaded`); + } +}); + +test("should decode JSX text like the TypeScript parser", () => { + for (const value of [ + "Hello", + " ", + " & " ' ", + "Hello", + "€", + "�", + "😀", + "A", + "𝕙", + "¬", + "&", + "&nbsp;", + ]) { + const { ast } = tseslint.parser.parseForESLint(`${value}`, { + filePath: "fixture.tsx", + }); + assert.equal( + decodeJsxText(value), + ast.body[0].expression.children[0].value, + value, + ); + } +}); + +const compatibilityCases = [ + ...["mjs", "cjs"].map((extension) => ({ + filename: `unused-underscores.${extension}`, + code: "const _unused=1; function _fn(_arg) {} try {} catch (_error) {}", + rule: "no-unused-vars", + oxlint: 4, + })), + ...["js", "jsx", "ts", "tsx"].flatMap((extension) => [ + { + filename: `ignored-underscore.${extension}`, + code: "const _unused=1;", + rule: "no-unused-vars", + eslintRule: extension.startsWith("ts") + ? "@typescript-eslint/no-unused-vars" + : "no-unused-vars", + oxlint: 0, + }, + { + filename: `bare-underscore.${extension}`, + code: "const _=1;", + rule: "no-unused-vars", + eslintRule: extension.startsWith("ts") + ? "@typescript-eslint/no-unused-vars" + : "no-unused-vars", + oxlint: 1, + }, + ]), + ...["js", "jsx"].map((extension) => ({ + filename: `es2021-globals.${extension}`, + code: "export const values=[WeakRef,AggregateError,FinalizationRegistry];", + rule: "no-undef", + oxlint: 0, + })), + { + filename: "jsx-unused.tsx", + code: 'import {Used,Unused} from "components"; import * as UI from "components"; export const Example=()=> <>;', + rule: "no-unused-vars", + eslintRule: "@typescript-eslint/no-unused-vars", + oxlint: 1, + }, + { + filename: "depend.ts", + code: 'import isNumber from "is-number"; export const check = isNumber;', + rule: "depend/ban-dependencies", + oxlint: 1, + }, + { + filename: "depend-subpath.ts", + code: 'import isNumber from "is-number/index.js"; export const check = isNumber;', + rule: "depend/ban-dependencies", + oxlint: 1, + }, + { + filename: "testing-debug.unit.spec.tsx", + code: 'import {screen} from "@testing-library/react"; test("example",()=>screen.debug());', + rule: "testing-library/no-debugging-utils", + oxlint: 1, + }, + { + filename: "testing-act.unit.spec.tsx", + code: 'import {act,render} from "@testing-library/react"; test("example",()=>act(()=>render(
)));', + rule: "testing-library/no-unnecessary-act", + oxlint: 1, + }, + { + filename: "testing-screen.unit.spec.tsx", + code: 'import {render} from "@testing-library/react"; test("example",()=>{const {getByText}=render(
);getByText("Hello");});', + rule: "testing-library/prefer-screen-queries", + oxlint: 1, + }, + { + filename: "focused-test.ts", + code: 'test.concurrent.only("example", () => {});', + rule: "no-only-tests/no-only-tests", + oxlint: 1, + }, + { + filename: "focused-test-disabled.ts", + code: '// eslint-disable-next-line no-only-tests/no-only-tests\ntest.concurrent.only("example", () => {});', + rule: "no-only-tests/no-only-tests", + oxlint: 0, + }, + { + filename: "ordinary-test.ts", + code: 'test("example", () => {}); const value = { only: true };', + rule: "no-only-tests/no-only-tests", + oxlint: 0, + }, + { + filename: "complexity-limit.ts", + code: "export function f(a){" + "if(a)a();".repeat(54) + "}", + rule: "complexity", + oxlint: 0, + }, + { + filename: "complexity-over-limit.ts", + code: "export function f(a){" + "if(a)a();".repeat(55) + "}", + rule: "complexity", + oxlint: 1, + }, + { + filename: "complexity-disabled.ts", + code: + "export const f=\n/* eslint-disable complexity */\n({a}\n)=>{\n/* eslint-enable complexity */\n" + + "if(a)a();".repeat(55) + + "};", + rule: "complexity", + oxlint: 0, + }, + { + filename: "module-global.js", + directory: "e2e/support", + code: 'const process = require("process");', + rule: "eslint-js/no-redeclare", + oxlint: 0, + }, + { + filename: "duplicate.js", + directory: "e2e/support", + code: "var process; var process;", + rule: "eslint-js/no-redeclare", + oxlint: 1, + }, + { + filename: "nested.js", + directory: "e2e/support", + code: "function f(){let name=1;return name;}", + rule: "eslint-js/no-redeclare", + oxlint: 0, + }, + { + filename: "translation.tsx", + code: "export function Example(){return
Hello world
;}", + rule: "i18next/no-literal-string", + oxlint: 1, + }, + { + filename: "translated.tsx", + code: "export function Example(){return
{t`Hello world`}
;}", + rule: "i18next/no-literal-string", + oxlint: 0, + }, + { + filename: "entities.tsx", + code: "export function Example(){return
 
;}", + rule: "i18next/no-literal-string", + oxlint: 0, + }, + { + filename: "encoded-translation.tsx", + code: "export function Example(){return
Hello
;}", + rule: "i18next/no-literal-string", + oxlint: 1, + }, + { + filename: "translation-exceptions.tsx", + code: "export function Example(){return <>
OK
Hello world;}", + rule: "i18next/no-literal-string", + oxlint: 0, + }, + { + filename: "translation-disable.tsx", + code: "export function Example(){return <>\n{/* eslint-disable i18next/no-literal-string */}\n
Billing address
\n{/* eslint-enable i18next/no-literal-string */}\n;}", + rule: "i18next/no-literal-string", + oxlint: 0, + }, + { + filename: "conditional.unit.spec.tsx", + code: 'it("example",()=>{if(value){expect(value).toBe(true);}});', + rule: "jest-js/no-conditional-expect", + oxlint: 1, + }, + { + filename: "helper.unit.spec.tsx", + code: "export function helper(){if(value){expect(value).toBe(true);}}", + rule: "jest-js/no-conditional-expect", + oxlint: 0, + }, + { + filename: "conditional-disabled.unit.spec.tsx", + code: 'it("example",()=>{if(value){\n// eslint-disable-next-line jest-js/no-conditional-expect\nexpect(value).toBe(true);}});', + rule: "jest-js/no-conditional-expect", + oxlint: 0, + }, + { + filename: "type-only.ts", + code: 'const token="value"; export type Token=typeof token;', + rule: "no-unused-vars", + oxlint: 0, + eslint: 1, + eslintRule: "@typescript-eslint/no-unused-vars", + }, + { + filename: "unused-disabled.ts", + code: '// eslint-disable-next-line @typescript-eslint/no-unused-vars -- intentional unused fixture\nconst token="value";', + rule: "no-unused-vars", + oxlint: 0, + eslint: 0, + eslintRule: "@typescript-eslint/no-unused-vars", + }, + { + filename: "unused.ts", + code: "const token=1;", + rule: "no-unused-vars", + oxlint: 1, + eslint: 1, + eslintRule: "@typescript-eslint/no-unused-vars", + }, + { + filename: "augmentation.ts", + code: 'declare module "fake" { interface ColumnMeta {wrap?:boolean;} }', + rule: "no-unused-vars", + oxlint: 0, + eslint: 2, + eslintRule: "@typescript-eslint/no-unused-vars", + }, + { + filename: "mixed-imports.ts", + code: 'import {type Props,a} from "example-module"; import {b} from "example-module"; export {a,b}; export type X=Props;', + rule: "import/no-duplicates", + oxlint: 1, + eslint: 0, + }, + { + filename: "combined-imports.ts", + code: 'import {type Props,a,b} from "example-module"; export {a,b}; export type X=Props;', + rule: "import/no-duplicates", + oxlint: 0, + }, + { + filename: "separate-types.ts", + code: 'import type {Props} from "example-module"; import {a,b} from "example-module"; export {a,b}; export type X=Props;', + rule: "import/no-duplicates", + oxlint: 0, + }, + { + filename: "unnamed-mock.unit.spec.tsx", + code: 'const {forwardRef}=jest.requireActual("react");export const Mock=forwardRef((props,ref)=>
);', + rule: "react/display-name", + oxlint: 1, + eslint: 0, + }, + { + filename: "named-mock.unit.spec.tsx", + code: 'const {forwardRef}=jest.requireActual("react");export const Mock=forwardRef((props,ref)=>
);Mock.displayName="Mock";', + rule: "react/display-name", + oxlint: 0, + }, + { + filename: "anonymous-hoc.tsx", + code: 'import React from "react";export const hoc=C=>class extends React.Component{render(){return }};', + rule: "react/display-name", + oxlint: 0, + eslint: 1, + }, + { + filename: "restricted-type.ts", + code: 'import type {ConfigType} from "dayjs"; export type Value=ConfigType;', + rule: "eslint-js/no-restricted-imports", + oxlint: 1, + }, + { + filename: "ttag.ts", + code: "export const text=t`Hello`;", + rule: "ttag/no-module-declaration", + oxlint: 1, + }, + { + filename: "ttag-block.ts", + code: "for(let i=0;i<1;i++) t`Hello`;", + rule: "ttag/no-module-declaration", + oxlint: 0, + }, + { + filename: "plain-class.tsx", + code: "export class Utility {componentWillMount(){}}", + rule: "react-js/no-deprecated", + oxlint: 0, + }, + { + filename: "react-class.tsx", + code: 'import React from "react"; export class Example extends React.Component{componentWillMount(){} render(){return null;}}', + rule: "react-js/no-deprecated", + oxlint: 1, + }, +]; + +// Oxlint names a rule as namespace(rule), with eslint standing in for the unprefixed rules. +function diagnosticCode(rule) { + const separator = rule.lastIndexOf("/"); + return separator === -1 + ? `eslint(${rule})` + : `${rule.slice(0, separator)}(${rule.slice(separator + 1)})`; +} + +test("should apply the configured rules and suppressions", async (t) => { + // Fixture paths must match the frontend and e2e policy overrides. + const directories = new Map(); + const directoryFor = (parent) => { + if (!directories.has(parent)) + directories.set( + parent, + fs.mkdtempSync(path.join(root, parent, "__lint-fixture-")), + ); + return directories.get(parent); + }; + t.after(() => { + for (const directory of directories.values()) + fs.rmSync(directory, { recursive: true, force: true }); + }); + const cases = compatibilityCases.map( + ({ + filename, + code, + directory = "frontend/src/metabase/utils", + rule, + oxlint, + eslint = oxlint, + eslintRule = rule, + }) => { + const absolute = path.join(directoryFor(directory), filename); + fs.writeFileSync(absolute, code); + return { filename: absolute, rule, oxlint, eslint, eslintRule }; + }, + ); + const result = spawnSync( + process.execPath, + [ + binary, + "--disable-nested-config", + "--report-unused-disable-directives", + "--format", + "json", + ...cases.map((c) => c.filename), + ], + { + cwd: root, + encoding: "utf8", + maxBuffer: 4 * 1024 * 1024, + timeout: 60_000, + }, + ); + assert.ifError(result.error); + assert.ok(result.status === 0 || result.status === 1, result.stderr); + const native = JSON.parse(result.stdout); + assert.equal( + native.number_of_files, + cases.length, + "Fixtures were ignored instead of linted", + ); + const nativeByFile = Map.groupBy(native.diagnostics, (diagnostic) => + path.resolve(root, diagnostic.filename), + ); + const eslint = new ESLint({ cwd: root }); + const legacy = await eslint.lintFiles(cases.map((c) => c.filename)); + const legacyByFile = new Map( + legacy.map((result) => [result.filePath, result.messages]), + ); + for (const fixture of cases) { + await t.test( + `should apply ${fixture.rule} to ${path.basename(fixture.filename)}`, + () => { + const findings = nativeByFile.get(fixture.filename) ?? []; + const code = diagnosticCode(fixture.rule); + assert.equal( + legacyByFile + .get(fixture.filename) + .filter((d) => d.ruleId === fixture.eslintRule).length, + fixture.eslint, + `ESLint ${fixture.filename}`, + ); + assert.equal( + findings.filter((d) => d.code === code).length, + fixture.oxlint, + `Oxlint ${fixture.filename}`, + ); + assert.equal( + findings.filter((d) => !d.code).length, + 0, + `Unused suppression: ${fixture.filename}`, + ); + }, + ); + } +}); diff --git a/frontend/lint/tests/oxlint-config.test.mjs b/frontend/lint/tests/oxlint-config.test.mjs new file mode 100644 index 00000000000..a9daa1005a3 --- /dev/null +++ b/frontend/lint/tests/oxlint-config.test.mjs @@ -0,0 +1,266 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +import { ESLint } from "eslint"; + +import policy from "../config.mjs"; +import { + createConfig, + jsRules, + root, + settingsForFile, +} from "../oxlint/config.mjs"; +import { wrap } from "../oxlint/plugin.mjs"; +import defaults from "../oxlint/rule-defaults.json" with { type: "json" }; +import presets from "../recommended-rules.json" with { type: "json" }; +import { + recommendedRules, + ruleDefaults, +} from "../update-recommended-rules.mjs"; + +const config = createConfig(); + +// wrap requires every rule the namespace declares, so the fixtures repeat one rule under each name. +const namespace = "metabase"; +const fakePlugin = (rule) => ({ + rules: Object.fromEntries(jsRules[namespace].map((name) => [name, rule])), +}); + +test("should match the installed presets and rule defaults", () => { + assert.deepEqual(presets, recommendedRules, "Run bun run lint-config-update"); + assert.deepEqual(defaults, ruleDefaults, "Run bun run lint-config-update"); +}); + +test("should match ESLint settings for each file scope", async () => { + const eslint = new ESLint({ cwd: root }); + for (const relative of [ + "frontend/src/metabase/new-module/Example.tsx", + "frontend/src/metabase/new-module/Example.unit.spec.tsx", + "frontend/src/metabase/new-module/Example.stories.tsx", + "frontend/src/metabase/new-module/Example.js", + "frontend/lint/oxlint/config.mjs", + "enterprise/frontend/src/metabase-enterprise/new-module/Example.tsx", + "enterprise/frontend/src/embedding-sdk-ee/new-module/Example.unit.spec.tsx", + "e2e/test/scenarios/new-module/example.cy.spec.ts", + "e2e/test-component/scenarios/new-module/example.cy.spec.tsx", + "e2e/support/new-module/helper.ts", + "frontend/test/new-module/setup.ts", + ]) { + const filename = path.join(root, relative); + const expected = await eslint.calculateConfigForFile(filename); + const actual = settingsForFile(filename); + assert.deepEqual( + actual.settings, + expected.settings ?? {}, + `${relative}: settings`, + ); + // sourceType belongs to languageOptions in ESLint's effective configuration. + const parserOptions = { ...expected.languageOptions.parserOptions }; + const actualParserOptions = { ...actual.parserOptions }; + delete parserOptions.sourceType; + delete actualParserOptions.sourceType; + assert.deepEqual( + actualParserOptions, + parserOptions, + `${relative}: parser options`, + ); + } +}); + +test("should read settings for each file through the reused context", () => { + const seen = []; + const rule = wrap( + namespace, + fakePlugin({ + create(context) { + seen.push(context); + return {}; + }, + }), + ).rules[jsRules[namespace][0]]; + // Oxlint hands a rule one context object for every file, changing only the filename. + let filename; + const context = { + get filename() { + return filename; + }, + }; + const files = [ + "e2e/test/scenarios/new-module/example.cy.spec.ts", + "frontend/src/metabase/new-module/Example.js", + "frontend/lint/oxlint/config.mjs", + ]; + for (const relative of files) { + filename = path.join(root, relative); + rule.create(context); + const expected = settingsForFile(filename); + const current = seen.at(-1); + assert.deepEqual(current.settings, expected.settings, relative); + assert.deepEqual(current.parserOptions, expected.parserOptions, relative); + } + assert.equal(new Set(seen).size, 1, "one derived context serves every file"); + assert.notDeepEqual( + settingsForFile(path.join(root, files[0])).settings, + settingsForFile(path.join(root, files[1])).settings, + ); + assert.notDeepEqual( + settingsForFile(path.join(root, files[1])).parserOptions, + settingsForFile(path.join(root, files[2])).parserOptions, + ); +}); + +test("should require create and omit createOnce from wrapped rules", () => { + const wrapped = wrap( + namespace, + fakePlugin({ create: () => ({}), createOnce: () => ({}) }), + ).rules; + for (const [name, rule] of Object.entries(wrapped)) { + assert.equal(Object.hasOwn(rule, "createOnce"), false, name); + } + for (const rule of [{ createOnce: () => ({}) }, { meta: {} }]) { + assert.throws( + () => wrap(namespace, fakePlugin(rule)), + new RegExp(`Wrapped rule requires create: ${namespace}/`), + ); + } +}); + +test("should retain inherited prefer-const and console options", () => { + const ts = config.overrides.filter((entry) => + entry.files.includes("**/*.ts"), + ); + const preference = ts.findLast((entry) => entry.rules["prefer-const"]); + assert.equal(preference.rules["prefer-const"][1].destructuring, "all"); + const e2e = config.overrides.find((entry) => + entry.files.includes("e2e/**/*.cy.spec.*"), + ); + assert.deepEqual(e2e.rules["no-console"][1].allow, [ + "warn", + "error", + "errorBuffer", + ]); +}); + +test("should keep TypeScript extension rules enabled when their base rules are disabled", () => { + const ts = config.overrides.findLast( + (entry) => entry.files.includes("**/*.ts") && entry.rules["no-unused-vars"], + ); + assert.equal(ts.rules["no-unused-vars"][0], "error"); + assert.equal(ts.rules["no-unused-vars"][1].varsIgnorePattern, "^_.+$"); +}); + +test("should reject unsupported boundary configuration overrides", () => { + for (const entry of [ + { settings: { "boundaries/root-path": "/elsewhere" } }, + { settings: { "boundaries/dependency-nodes": ["require"] } }, + { rules: { "boundaries/element-types": ["error", { default: "allow" }] } }, + ]) { + policy.push(entry); + try { + assert.throws(() => createConfig(), /boundary checker/); + } finally { + policy.pop(); + } + } +}); + +test("should reject a later base-rule disable without an explicit TypeScript setting", () => { + for (const severity of ["off", 0]) { + policy.push({ files: ["**/*.ts"], rules: { "no-unused-vars": severity } }); + try { + assert.throws( + () => createConfig(), + /Disabling no-unused-vars after @typescript-eslint\/no-unused-vars requires an explicit/, + ); + } finally { + policy.pop(); + } + } + for (const severity of ["off", "error"]) { + policy.push({ + files: ["**/*.ts"], + rules: { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": severity, + }, + }); + try { + const actual = createConfig().overrides.findLast((entry) => + entry.files.includes("**/*.ts"), + ).rules["no-unused-vars"]; + assert.equal(Array.isArray(actual) ? actual[0] : actual, severity); + } finally { + policy.pop(); + } + } +}); + +test("should keep config-file globs rooted like ESLint", async (t) => { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), "oxlint-glob-test-")); + t.after(() => fs.rmSync(directory, { recursive: true, force: true })); + const files = [ + "example.config.js", + "nested/example.config.js", + "rspack.example.js", + "nested/rspack.example.js", + ".storybook/example.cjs", + "nested/.storybook/example.cjs", + "bin/example.js", + "nested/bin/example.js", + ]; + for (const file of files) { + const filename = path.join(directory, file); + fs.mkdirSync(path.dirname(filename), { recursive: true }); + fs.writeFileSync(filename, "debugger;"); + } + const rules = { "no-debugger": "error" }; + const original = policy.find((entry) => entry.files?.includes("*.config.js")); + const translated = config.overrides.find((entry) => + entry.files.includes(".storybook/*.cjs"), + ); + fs.writeFileSync( + path.join(directory, "config.json"), + JSON.stringify({ + categories: { correctness: "off" }, + overrides: [{ ...translated, rules }], + }), + ); + const result = spawnSync( + process.execPath, + [ + path.join(root, "node_modules/oxlint/bin/oxlint"), + "--disable-nested-config", + "--config", + "config.json", + "--format", + "json", + ...files, + ], + { cwd: directory, encoding: "utf8", timeout: 30_000 }, + ); + assert.ifError(result.error); + assert.equal(result.status, 1, result.stderr); + const actual = JSON.parse(result.stdout); + assert.equal(actual.number_of_files, files.length); + const eslint = new ESLint({ + cwd: directory, + overrideConfigFile: true, + overrideConfig: [{ ...original, rules }], + }); + const expected = await eslint.lintFiles(files); + for (const file of files) { + const legacy = expected.find( + (entry) => entry.filePath === path.join(directory, file), + ); + const native = actual.diagnostics.filter( + (entry) => entry.filename === file, + ); + const count = file.startsWith("nested/") ? 0 : 1; + assert.equal(legacy.messages.length, count, `ESLint ${file}`); + assert.equal(native.length, count, `Oxlint ${file}`); + } +}); diff --git a/frontend/lint/tests/oxlint-import-resolver.test.mjs b/frontend/lint/tests/oxlint-import-resolver.test.mjs new file mode 100644 index 00000000000..1257e31b963 --- /dev/null +++ b/frontend/lint/tests/oxlint-import-resolver.test.mjs @@ -0,0 +1,350 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import { createRequire } from "node:module"; +import { execFileSync } from "node:child_process"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +import { ResolverFactory } from "oxc-resolver"; + +import { relative as upstreamImportX } from "eslint-plugin-import-x/utils/resolve"; + +import { createImportResolverService } from "../oxlint-import-resolver.mjs"; + +const repository = path.resolve(import.meta.dirname, "../../.."); + +const require = createRequire(import.meta.url); +const { relative: upstreamLegacy } = require("eslint-module-utils/resolve"); + +function fixture(t) { + // Upstream resolvers report real paths, so the fixture tree must be free of symlinked parents. + const root = fs.realpathSync( + fs.mkdtempSync(path.join(os.tmpdir(), "metabase-resolver-test-")), + ); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + function write(file, content = "export default 1;\n") { + const filename = path.join(root, file); + fs.mkdirSync(path.dirname(filename), { recursive: true }); + fs.writeFileSync(filename, content); + return filename; + } + write("package.json", JSON.stringify({ name: "resolver-fixture" })); + // The webpack resolver chooses enhanced-resolve through the fixture's webpack installation. + fs.mkdirSync(path.join(root, "node_modules")); + fs.symlinkSync( + path.dirname(require.resolve("webpack/package.json")), + path.join(root, "node_modules/webpack"), + "dir", + ); + const file = write("src/importer.ts"); + write("src/sibling.ts"); + write("src/CaseSensitive.ts"); + write("src/directory/index.tsx"); + write("src/style.css"); + write("src/literal?name.js"); + write("src/choice.js"); + write("src/choice.ts"); + write("other/sibling.ts"); + write("other/importer.ts"); + write("src/browser.js"); + write("src/fallback.js"); + const config = write( + "webpack.config.cjs", + `module.exports = ${JSON.stringify({ + resolve: { + extensions: [".js", ".jsx", ".ts", ".tsx", ".css", ".svg"], + alias: { app: path.join(root, "src") }, + fallback: { "fixture-fallback": path.join(root, "src/fallback.js") }, + }, + externals: { "fixture-external": "EXTERNAL" }, + })};`, + ); + const resolverConfig = { node: true, webpack: { config, typescript: true } }; + const settings = { + "import-x/resolver": resolverConfig, + "import/resolver": resolverConfig, + }; + const service = createImportResolverService({ ResolverFactory }); + return { root, file, settings, service, write }; +} + +function compare({ file, settings, service }, sources) { + const context = { + filename: file, + physicalFilename: file, + settings, + parserOptions: {}, + cwd: process.cwd(), + }; + for (const source of sources) { + assert.equal( + service.forSettings(settings).resolve(source, file).path, + upstreamImportX(source, file, settings, context), + `import-x: ${source}`, + ); + assert.equal( + service.resolveImport(source, context), + upstreamLegacy(source, file, settings), + `legacy: ${source}`, + ); + } +} + +test("should match upstream aliases, extensions, loaders, externals and builtins", (t) => { + const data = fixture(t); + compare(data, [ + "node:fs", + "fs", + "data:text/javascript,export default 1", + "./sibling", + "./directory", + "./choice", + "./style.css", + "./style.css?inline", + "./literal?name.js", + "./CaseSensitive", + "./casesensitive", + "./missing", + "app/sibling", + "app/directory", + "app/missing", + "fixture-loader!app/style.css?inline", + "fixture-external", + "fixture-fallback", + path.join(data.root, "src/sibling.ts"), + ]); + assert.equal( + data.service.forSettings(data.settings).resolve("./missing", data.file) + .found, + false, + ); +}); + +test("should match upstream package entries and symlink paths", (t) => { + const data = fixture(t); + const { write, root, service, settings, file } = data; + write( + "node_modules/fixture-package/package.json", + JSON.stringify({ + name: "fixture-package", + exports: { ".": { import: "./esm.js", require: "./cjs.js" } }, + main: "./cjs.js", + }), + ); + write("node_modules/fixture-package/esm.js"); + write("node_modules/fixture-package/cjs.js"); + write("node_modules/fixture-package/private.js"); + write( + "node_modules/fixture-jsnext/package.json", + JSON.stringify({ + name: "fixture-jsnext", + module: "./missing.js", + "jsnext:main": "./jsnext.js", + main: "./missing-too.js", + }), + ); + write("node_modules/fixture-jsnext/jsnext.js"); + fs.symlinkSync( + path.join(root, "node_modules/fixture-package"), + path.join(root, "node_modules/fixture-linked"), + "dir", + ); + compare(data, [ + "fixture-package", + "fixture-package/private.js", + "fixture-linked", + "fixture-jsnext", + ]); + assert.equal( + service.forSettings(settings).resolve("fixture-package", file).path, + path.join(root, "node_modules/fixture-package/esm.js"), + ); + assert.equal( + service.forSettings(settings, "import").resolve("fixture-package", file) + .path, + path.join(root, "node_modules/fixture-package/cjs.js"), + ); +}); + +test("should distinguish source directories and resolver configurations", (t) => { + const data = fixture(t); + const otherFile = path.join(data.root, "other/importer.ts"); + compare({ ...data, file: otherFile }, ["./sibling", "app/sibling"]); + assert.notEqual( + data.service.forSettings(data.settings).resolve("./sibling", data.file) + .path, + data.service.forSettings(data.settings).resolve("./sibling", otherFile) + .path, + ); + compare({ ...data, settings: {} }, ["app/sibling", "./sibling", "node:fs"]); + assert.equal( + data.service.forSettings({}).resolve("app/sibling", data.file).found, + false, + ); +}); + +test("should reuse bare imports within each package root", (t) => { + const data = fixture(t); + const { root, settings, service, write } = data; + write( + "node_modules/fixture-shared/package.json", + JSON.stringify({ name: "fixture-shared", main: "./outer.js" }), + ); + write("node_modules/fixture-shared/outer.js"); + write("src/deep/importer.ts"); + // A nested node_modules gives the same bare specifier a different target. + write("nested/package.json", JSON.stringify({ name: "nested-fixture" })); + write("nested/importer.ts"); + write( + "nested/node_modules/fixture-shared/package.json", + JSON.stringify({ name: "fixture-shared", main: "./inner.js" }), + ); + write("nested/node_modules/fixture-shared/inner.js"); + write("plain/package.json", JSON.stringify({ name: "plain-fixture" })); + write("plain/importer.ts"); + + const files = { + shallow: data.file, + deep: path.join(root, "src/deep/importer.ts"), + nested: path.join(root, "nested/importer.ts"), + plain: path.join(root, "plain/importer.ts"), + }; + for (const file of Object.values(files)) { + compare({ ...data, file }, [ + "fixture-shared", + "fixture-shared?raw", + "fixture-shared/inner.js", + "./sibling", + "app/sibling", + "fixture-loader!./sibling.ts", + ]); + } + + let nativeCalls = 0; + class CountingResolver { + constructor(options) { + this.inner = new ResolverFactory(options); + } + sync(directory, source) { + nativeCalls++; + return this.inner.sync(directory, source); + } + } + const counted = createImportResolverService({ + ResolverFactory: CountingResolver, + }).forSettings(settings); + for (const file of [ + files.shallow, + files.deep, + files.nested, + files.plain, + files.shallow, + ]) { + counted.resolve("fixture-shared", file); + } + assert.equal(nativeCalls, 3, "one native resolution per package root"); + + const outer = path.join(root, "node_modules/fixture-shared/outer.js"); + const inner = path.join(root, "nested/node_modules/fixture-shared/inner.js"); + for (const { source, from, expected } of [ + { source: "fixture-shared", from: "shallow", expected: outer }, + { source: "fixture-shared", from: "deep", expected: outer }, + { source: "fixture-shared", from: "plain", expected: outer }, + { source: "fixture-shared", from: "nested", expected: inner }, + { source: "fixture-shared?raw", from: "shallow", expected: `${outer}?raw` }, + { source: "fixture-shared?raw", from: "nested", expected: `${inner}?raw` }, + { + source: "./sibling", + from: "shallow", + expected: path.join(root, "src/sibling.ts"), + }, + { source: "./sibling", from: "deep", expected: undefined }, + { + source: "fixture-loader!./sibling.ts", + from: "shallow", + expected: path.join(root, "src/sibling.ts"), + }, + { + source: "fixture-loader!./sibling.ts", + from: "deep", + expected: undefined, + }, + ]) { + assert.equal( + service.forSettings(settings).resolve(source, files[from]).path, + expected, + `${source} from ${from}`, + ); + } +}); + +test("should find newly created files through a fresh service", (t) => { + const data = fixture(t); + const before = data.service.forSettings(data.settings); + assert.equal(before.resolve("app/created", data.file).found, false); + data.write("src/created.ts"); + const after = createImportResolverService({ ResolverFactory }); + assert.equal( + after.forSettings(data.settings).resolve("app/created", data.file).path, + path.join(data.root, "src/created.ts"), + ); +}); + +test("should reject unsupported resolver policies", (t) => { + const service = createImportResolverService({ ResolverFactory }); + for (const settings of [ + { "import-x/resolver-next": [] }, + { "import-x/resolve": { extensions: [".custom"] } }, + { "import-x/resolver": { node: { extensions: [".custom"] } } }, + { "import-x/resolver": { node: true, typescript: true } }, + ]) { + assert.throws(() => service.forSettings(settings), /Unsupported resolver/); + } + const data = fixture(t); + for (const { file, source, error } of [ + { + file: "relative-alias.config.cjs", + source: 'module.exports = { resolve: { alias: { app: "./src" } } };', + error: /Unsupported resolver alias/, + }, + { + file: "relative-fallback.config.cjs", + source: 'module.exports = { resolve: { fallback: { app: "./src" } } };', + error: /Unsupported resolver alias/, + }, + { + file: "unsupported.config.cjs", + source: "module.exports = { resolve: { plugins: [{}] } };", + error: /Unsupported Rspack resolve option/, + }, + ]) { + const config = data.write(file, source); + assert.throws( + () => + data.service.forSettings({ + ...data.settings, + "import-x/resolver": { node: true, webpack: { config } }, + }), + error, + ); + } +}); + +for (const WEBPACK_BUNDLE of ["development", "production"]) { + for (const MB_EDITION of ["oss", "ee"]) { + test(`should match build aliases in ${WEBPACK_BUNDLE}/${MB_EDITION}`, () => { + // Build settings are evaluated at module load, so each environment needs a fresh process. + execFileSync( + process.execPath, + [path.join(import.meta.dirname, "fixtures/resolve-config-parity.js")], + { + cwd: repository, + env: { ...process.env, WEBPACK_BUNDLE, MB_EDITION }, + encoding: "utf8", + timeout: 60_000, + }, + ); + }); + } +} diff --git a/frontend/lint/tests/rule-tester.js b/frontend/lint/tests/rule-tester.js new file mode 100644 index 00000000000..e9fb7d9a1b0 --- /dev/null +++ b/frontend/lint/tests/rule-tester.js @@ -0,0 +1,12 @@ +import { RuleTester } from "oxlint/plugins-dev"; + +// `lang` parses every case without a filename as TSX. A case that sets `filename` +// takes its language from the extension instead. +export function createRuleTester() { + return new RuleTester({ + languageOptions: { + sourceType: "module", + parserOptions: { lang: "tsx" }, + }, + }); +} diff --git a/frontend/lint/tests/valid-theme-tokens.unit.spec.js b/frontend/lint/tests/valid-theme-tokens.unit.spec.js index 12f3c257aa3..0eae61401fd 100644 --- a/frontend/lint/tests/valid-theme-tokens.unit.spec.js +++ b/frontend/lint/tests/valid-theme-tokens.unit.spec.js @@ -1,16 +1,8 @@ -import { RuleTester } from "eslint"; - import rule from "../eslint-plugin-metabase/rules/valid-theme-tokens"; -const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 2015, - sourceType: "module", - parserOptions: { - ecmaFeatures: { jsx: true }, - }, - }, -}); +import { createRuleTester } from "./rule-tester"; + +const ruleTester = createRuleTester(); const VALID_CASES = [ { diff --git a/frontend/lint/update-recommended-rules.mjs b/frontend/lint/update-recommended-rules.mjs new file mode 100644 index 00000000000..2ab76078e6f --- /dev/null +++ b/frontend/lint/update-recommended-rules.mjs @@ -0,0 +1,54 @@ +import fs from "node:fs"; +import { pathToFileURL } from "node:url"; +import { builtinRules } from "eslint/use-at-your-own-risk"; +import js from "@eslint/js"; +import tseslint from "typescript-eslint"; +import configs from "../../eslint.config.mjs"; +import ruleMap from "./oxlint/rule-map.json" with { type: "json" }; + +function stripConfig(config) { + const copy = { ...config }; + delete copy.plugins; + if (copy.languageOptions) { + copy.languageOptions = { ...copy.languageOptions }; + delete copy.languageOptions.parser; + } + return copy; +} +const plugins = Object.assign({}, ...configs.map((config) => config.plugins)); +export const recommendedRules = { + javascript: js.configs.recommended.rules, + react: plugins.react.configs.recommended.rules, + jsxRuntime: plugins.react.configs["jsx-runtime"].rules, + hooks: plugins["react-hooks"].configs.recommended.rules, + i18next: plugins.i18next.configs["flat/recommended"].rules, + typescript: tseslint.configs.recommended.map(stripConfig), + jest: plugins.jest.configs.recommended.rules, + jestDom: plugins["jest-dom"].configs.recommended.rules, + testingLibrary: plugins["testing-library"].configs.react.rules, + storybook: plugins.storybook.configs["flat/recommended"].map(stripConfig), +}; +export const ruleDefaults = Object.fromEntries( + Object.keys(ruleMap).flatMap((name) => { + const slash = name.lastIndexOf("/"); + const rule = + slash === -1 + ? builtinRules.get(name) + : plugins[name.slice(0, slash)]?.rules[name.slice(slash + 1)]; + return rule?.meta.defaultOptions ? [[name, rule.meta.defaultOptions]] : []; + }), +); +if ( + process.argv[1] && + import.meta.url === pathToFileURL(process.argv[1]).href +) { + for (const [filename, value] of [ + ["./recommended-rules.json", recommendedRules], + ["./oxlint/rule-defaults.json", ruleDefaults], + ]) { + fs.writeFileSync( + new URL(filename, import.meta.url), + JSON.stringify(value, null, 2) + "\n", + ); + } +} diff --git a/frontend/src/embedding-sdk-bundle/analytics/snowplow.ts b/frontend/src/embedding-sdk-bundle/analytics/snowplow.ts index 158be39b086..9e04977ec2d 100644 --- a/frontend/src/embedding-sdk-bundle/analytics/snowplow.ts +++ b/frontend/src/embedding-sdk-bundle/analytics/snowplow.ts @@ -6,8 +6,8 @@ import { import { version as reactVersion } from "react"; import type { SdkStoreState } from "embedding-sdk-bundle/store/types"; -import { isEmbedPreviewRequest } from "metabase/embedding/lib/auth/set-embed-preview-header"; import { EMBEDDING_SDK_CONFIG } from "metabase/embedding-sdk/config"; +import { isEmbedPreviewRequest } from "metabase/embedding/lib/auth/set-embed-preview-header"; import { getSettings } from "metabase/settings"; import { trackMetaplowEvent } from "metabase/utils/metaplow"; import type { SimpleEventSchema } from "metabase-types/analytics/event"; diff --git a/frontend/src/embedding-sdk-bundle/bootstrap-auth.ts b/frontend/src/embedding-sdk-bundle/bootstrap-auth.ts index c4f5229e257..72e8059ea7d 100644 --- a/frontend/src/embedding-sdk-bundle/bootstrap-auth.ts +++ b/frontend/src/embedding-sdk-bundle/bootstrap-auth.ts @@ -9,15 +9,15 @@ * bundle since the bootstrap entry is excluded from splitChunks. */ +import type { MetabaseProviderPropsStoreExternalProps } from "embedding-sdk-bundle/components/public/ComponentProvider"; +import * as MetabaseError from "embedding-sdk-shared/errors"; +import type { MetabaseProviderPropsStore } from "embedding-sdk-shared/lib/ensure-metabase-provider-props-store"; +import type { SdkAuthState } from "embedding-sdk-shared/types/auth-state"; import { connectToInstanceAuthSso, jwtDefaultRefreshTokenFunction, validateSession, } from "embedding/auth-common"; -import type { MetabaseProviderPropsStoreExternalProps } from "embedding-sdk-bundle/components/public/ComponentProvider"; -import * as MetabaseError from "embedding-sdk-shared/errors"; -import type { MetabaseProviderPropsStore } from "embedding-sdk-shared/lib/ensure-metabase-provider-props-store"; -import type { SdkAuthState } from "embedding-sdk-shared/types/auth-state"; const SDK_AUTH_STATE_KEY = "METABASE_EMBEDDING_SDK_AUTH_STATE"; diff --git a/frontend/src/embedding-sdk-bundle/components/private/PublicComponentStylesWrapper.tsx b/frontend/src/embedding-sdk-bundle/components/private/PublicComponentStylesWrapper.tsx index a5012d8ca97..b99e8541c63 100644 --- a/frontend/src/embedding-sdk-bundle/components/private/PublicComponentStylesWrapper.tsx +++ b/frontend/src/embedding-sdk-bundle/components/private/PublicComponentStylesWrapper.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports -- We sometimes need css-in-js in the SDK +// eslint-disable-next-line eslint-js/no-restricted-imports -- We sometimes need css-in-js in the SDK import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports -- We sometimes need css-in-js in the SDK +// eslint-disable-next-line eslint-js/no-restricted-imports -- We sometimes need css-in-js in the SDK import styled from "@emotion/styled"; import cx from "classnames"; import type React from "react"; diff --git a/frontend/src/embedding-sdk-bundle/components/private/SdkGlobalFontsStyles.tsx b/frontend/src/embedding-sdk-bundle/components/private/SdkGlobalFontsStyles.tsx index 74cc07c3141..cfb24d20883 100644 --- a/frontend/src/embedding-sdk-bundle/components/private/SdkGlobalFontsStyles.tsx +++ b/frontend/src/embedding-sdk-bundle/components/private/SdkGlobalFontsStyles.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports -- We sometimes need css-in-js in the SDK +// eslint-disable-next-line eslint-js/no-restricted-imports -- We sometimes need css-in-js in the SDK import { Global, css } from "@emotion/react"; import { useMemo } from "react"; diff --git a/frontend/src/embedding-sdk-bundle/components/private/SdkQuestion/components/Breakout/use-breakout-data.ts b/frontend/src/embedding-sdk-bundle/components/private/SdkQuestion/components/Breakout/use-breakout-data.ts index 42424c2b371..c8334fb86f1 100644 --- a/frontend/src/embedding-sdk-bundle/components/private/SdkQuestion/components/Breakout/use-breakout-data.ts +++ b/frontend/src/embedding-sdk-bundle/components/private/SdkQuestion/components/Breakout/use-breakout-data.ts @@ -1,8 +1,8 @@ import { type BreakoutListItem, getBreakoutListItem, + useBreakoutQueryHandlers, } from "metabase/query_builder"; -import { useBreakoutQueryHandlers } from "metabase/query_builder"; import { isNotNull } from "metabase/utils/types"; import * as Lib from "metabase-lib"; import type Question from "metabase-lib/v1/Question"; diff --git a/frontend/src/embedding-sdk-bundle/components/private/SdkQuestion/context/SdkQuestionProvider.tsx b/frontend/src/embedding-sdk-bundle/components/private/SdkQuestion/context/SdkQuestionProvider.tsx index e74e536ae7b..faab7d64ad0 100644 --- a/frontend/src/embedding-sdk-bundle/components/private/SdkQuestion/context/SdkQuestionProvider.tsx +++ b/frontend/src/embedding-sdk-bundle/components/private/SdkQuestion/context/SdkQuestionProvider.tsx @@ -36,14 +36,14 @@ import { } from "embedding-sdk-bundle/store/selectors"; import type { NavigateToNewCardParams } from "embedding-sdk-bundle/types"; import type { MetabasePluginsConfig } from "embedding-sdk-bundle/types/plugins"; -import { EmbeddingEntityContextProvider } from "metabase/embedding/context"; import { transformSdkQuestion } from "metabase/embedding-sdk/lib/transform-question"; import type { MetabasePluginsConfig as InternalMetabasePluginsConfig } from "metabase/embedding-sdk/types/plugins"; +import { EmbeddingEntityContextProvider } from "metabase/embedding/context"; import { type OnCreateOptions, useCreateQuestion, + useSaveQuestion, } from "metabase/query_builder"; -import { useSaveQuestion } from "metabase/query_builder"; import { EmbeddingDataPickerContextProvider } from "metabase/querying/notebook/components/NotebookDataPicker/EmbeddingDataPicker/context"; import * as Lib from "metabase-lib"; import type Question from "metabase-lib/v1/Question"; diff --git a/frontend/src/embedding-sdk-bundle/components/private/SdkThemeProvider.tsx b/frontend/src/embedding-sdk-bundle/components/private/SdkThemeProvider.tsx index 6556ebbd993..a4021f182e3 100644 --- a/frontend/src/embedding-sdk-bundle/components/private/SdkThemeProvider.tsx +++ b/frontend/src/embedding-sdk-bundle/components/private/SdkThemeProvider.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports -- We sometimes need css-in-js in the SDK +// eslint-disable-next-line eslint-js/no-restricted-imports -- We sometimes need css-in-js in the SDK import { Global } from "@emotion/react"; import { useContext, useId, useMemo } from "react"; diff --git a/frontend/src/embedding-sdk-bundle/components/public/ComponentProvider/ComponentProvider.tsx b/frontend/src/embedding-sdk-bundle/components/public/ComponentProvider/ComponentProvider.tsx index 2aa5eda08e4..aec0e80f083 100644 --- a/frontend/src/embedding-sdk-bundle/components/public/ComponentProvider/ComponentProvider.tsx +++ b/frontend/src/embedding-sdk-bundle/components/public/ComponentProvider/ComponentProvider.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports -- We sometimes need css-in-js in the SDK +// eslint-disable-next-line eslint-js/no-restricted-imports -- We sometimes need css-in-js in the SDK import { Global } from "@emotion/react"; import { type JSX, memo, useEffect, useId, useRef } from "react"; @@ -23,9 +23,9 @@ import type { MetabaseProviderProps } from "embedding-sdk-bundle/types/metabase- import { EnsureSingleInstance } from "embedding-sdk-shared/components/EnsureSingleInstance/EnsureSingleInstance"; import type { MetabaseProviderPropsStoreInternalProps } from "embedding-sdk-shared/lib/ensure-metabase-provider-props-store"; import { useInstanceLocale } from "metabase/common/hooks/use-instance-locale"; -import { LocaleProvider } from "metabase/embedding/LocaleProvider"; import { isEmbeddingEajs } from "metabase/embedding-sdk/config"; import { isEmbeddingThemeV1 } from "metabase/embedding-sdk/theme"; +import { LocaleProvider } from "metabase/embedding/LocaleProvider"; import { MetabaseReduxProvider, useSelector } from "metabase/redux"; import { setOptions } from "metabase/redux/embed"; import { getSetting } from "metabase/settings"; diff --git a/frontend/src/embedding-sdk-bundle/components/public/dashboard/SdkDashboard.tsx b/frontend/src/embedding-sdk-bundle/components/public/dashboard/SdkDashboard.tsx index 47e1ca151a4..ddb0840ec34 100644 --- a/frontend/src/embedding-sdk-bundle/components/public/dashboard/SdkDashboard.tsx +++ b/frontend/src/embedding-sdk-bundle/components/public/dashboard/SdkDashboard.tsx @@ -75,9 +75,9 @@ import { getIsEditing, } from "metabase/dashboard/selectors"; import type { RefreshPeriod } from "metabase/dashboard/types"; +import type { ParameterValues } from "metabase/embedding-sdk/types/dashboard"; import { EmbeddingEntityContextProvider } from "metabase/embedding/context"; import EmbedFrameS from "metabase/embedding/theme.module.css"; -import type { ParameterValues } from "metabase/embedding-sdk/types/dashboard"; import { useSelector } from "metabase/redux"; import { resetErrorPage, setErrorPage } from "metabase/redux/app"; import { dismissAllUndo } from "metabase/redux/undo"; diff --git a/frontend/src/embedding-sdk-bundle/hooks/private/use-init-data/use-init-data-internal.ts b/frontend/src/embedding-sdk-bundle/hooks/private/use-init-data/use-init-data-internal.ts index e222f509656..bd786952f4c 100644 --- a/frontend/src/embedding-sdk-bundle/hooks/private/use-init-data/use-init-data-internal.ts +++ b/frontend/src/embedding-sdk-bundle/hooks/private/use-init-data/use-init-data-internal.ts @@ -23,14 +23,14 @@ import type { MetabaseAuthConfig } from "embedding-sdk-bundle/types"; import { getSdkPackageVersion } from "embedding-sdk-shared/lib/get-build-info"; import { PLUGIN_API, type RequestClientInfo, api } from "metabase/api/client"; import { registerDashboardVisualizations } from "metabase/dashboard/visualizations/register"; -import { setDataApp } from "metabase/embedding/config"; -import { setEmbedPreviewHeader } from "metabase/embedding/lib/auth/set-embed-preview-header"; -import { setReactSdkEmbedReferrerHeader } from "metabase/embedding/lib/auth/set-react-sdk-embed-referrer-header"; -import { setRequestClientHeaders } from "metabase/embedding/lib/auth/set-request-client-headers"; import { EMBEDDING_SDK_CONFIG, isEmbeddingEajs, } from "metabase/embedding-sdk/config"; +import { setDataApp } from "metabase/embedding/config"; +import { setEmbedPreviewHeader } from "metabase/embedding/lib/auth/set-embed-preview-header"; +import { setReactSdkEmbedReferrerHeader } from "metabase/embedding/lib/auth/set-react-sdk-embed-referrer-header"; +import { setRequestClientHeaders } from "metabase/embedding/lib/auth/set-request-client-headers"; import { setBasename } from "metabase/utils/basename"; import { registerVisualizations } from "metabase/visualizations/register"; diff --git a/frontend/src/embedding-sdk-bundle/hooks/private/use-log-version-info/use-log-version-info.unit.spec.tsx b/frontend/src/embedding-sdk-bundle/hooks/private/use-log-version-info/use-log-version-info.unit.spec.tsx index d257d9c78ab..13c3daa5cf2 100644 --- a/frontend/src/embedding-sdk-bundle/hooks/private/use-log-version-info/use-log-version-info.unit.spec.tsx +++ b/frontend/src/embedding-sdk-bundle/hooks/private/use-log-version-info/use-log-version-info.unit.spec.tsx @@ -1,6 +1,6 @@ import { renderHook } from "@testing-library/react"; -import "embedding-sdk-bundle"; +import "embedding-sdk-bundle"; import { EMBEDDING_SDK_BUNDLE_UNKNOWN_VERSION } from "build-configs/embedding-sdk/constants/versions"; import { useLogVersionInfo } from "embedding-sdk-bundle/hooks/private/use-log-version-info"; import { getIsLocalhost } from "embedding-sdk-bundle/lib/get-is-localhost"; diff --git a/frontend/src/embedding-sdk-bundle/index.ts b/frontend/src/embedding-sdk-bundle/index.ts index 9e3a644f85a..81206363945 100644 --- a/frontend/src/embedding-sdk-bundle/index.ts +++ b/frontend/src/embedding-sdk-bundle/index.ts @@ -1,9 +1,9 @@ -/* eslint-disable import/order */ - // Must run before any dynamic import(): sets webpack's runtime publicPath so // on-demand chunks load from the Metabase-hosted SDK asset directory. +// oxfmt-ignore import "./lib/sdk-public-path"; +// oxfmt-ignore import { EMBEDDING_SDK_CONFIG } from "metabase/embedding-sdk/config"; // Enable SDK mode as we are in the SDK bundle @@ -11,16 +11,21 @@ import { EMBEDDING_SDK_CONFIG } from "metabase/embedding-sdk/config"; EMBEDDING_SDK_CONFIG.isEmbeddingSdk = true; // Import the embedding SDK vendors side-effects +// oxfmt-ignore import "metabase/embedding-sdk/vendors-side-effects"; // Import the EE plugins required by the embedding sdk. +// oxfmt-ignore import { initializePlugins } from "sdk-ee-plugins"; initializePlugins(); +// oxfmt-ignore import { sdkBundleExports } from "./sdk-bundle-exports"; +// oxfmt-ignore import { defineBuildInfo } from "metabase/embedding-sdk/lib/define-build-info"; +// oxfmt-ignore import { SDK_BUNDLE_LOADED } from "embedding-sdk-shared/constants/event-names"; defineBuildInfo("METABASE_EMBEDDING_SDK_BUNDLE_BUILD_INFO"); diff --git a/frontend/src/embedding-sdk-bundle/lib/sdk-question/resolve-deserialized-card.ts b/frontend/src/embedding-sdk-bundle/lib/sdk-question/resolve-deserialized-card.ts index 61b10a3141f..d69dd3e9e56 100644 --- a/frontend/src/embedding-sdk-bundle/lib/sdk-question/resolve-deserialized-card.ts +++ b/frontend/src/embedding-sdk-bundle/lib/sdk-question/resolve-deserialized-card.ts @@ -1,5 +1,5 @@ -import { resolveCardProp } from "embedding-sdk-bundle/lib/sdk-question/resolve-card-prop"; import { getCardFromSdkQuestionQuery } from "embedding-sdk-bundle/lib/sdk-question-query"; +import { resolveCardProp } from "embedding-sdk-bundle/lib/sdk-question/resolve-card-prop"; import type { UnsavedCard } from "metabase-types/api"; export function resolveDeserializedCard({ diff --git a/frontend/src/embedding-sdk-bundle/sdk-bundle-exports.ts b/frontend/src/embedding-sdk-bundle/sdk-bundle-exports.ts index 189a3e04f92..dc392844b95 100644 --- a/frontend/src/embedding-sdk-bundle/sdk-bundle-exports.ts +++ b/frontend/src/embedding-sdk-bundle/sdk-bundle-exports.ts @@ -1,43 +1,41 @@ -/* eslint-disable import/order */ - -import type { MetabaseEmbeddingSdkBundleExports } from "./types/sdk-bundle"; +import { DateRangeCalendar } from "embedding-sdk-bundle/components/public/DateRangeCalendar/DateRangeCalendar"; +import { DateRangePopover } from "embedding-sdk-bundle/components/public/DateRangePopover/DateRangePopover"; +import { useLogVersionInfo } from "embedding-sdk-bundle/hooks/private/use-log-version-info"; +import { createDashboard } from "embedding-sdk-bundle/lib/create-dashboard"; +import { resolveDatasetQuery } from "embedding-sdk-bundle/lib/create-metabase-query"; +import { executeAction } from "embedding-sdk-bundle/lib/execute-action"; +import { + formatDate, + formatDateRange, +} from "embedding-sdk-bundle/lib/format-date-range"; +import { queryDataset } from "embedding-sdk-bundle/lib/query-dataset"; +import { queryQuestion } from "embedding-sdk-bundle/lib/query-question"; +import { validateFunctionSchema } from "embedding-sdk-bundle/lib/validate-function-schema"; +import { + getAvailableFonts, + getLoginStatus, +} from "embedding-sdk-bundle/store/selectors"; +import { getUser } from "metabase/current-user"; +import { getApplicationName } from "metabase/selectors/whitelabel"; import { MetabotSubscriber } from "./components/private/MetabotSubscriber/MetabotSubscriber"; import { SdkThemeProviderWithStore } from "./components/private/SdkThemeProvider"; import { CollectionBrowser } from "./components/public/CollectionBrowser"; +import { ComponentProvider } from "./components/public/ComponentProvider"; import { CreateDashboardModal } from "./components/public/CreateDashboardModal"; import { CreateQuestion } from "./components/public/CreateQuestion"; import { InteractiveQuestion } from "./components/public/InteractiveQuestion"; -import { ComponentProvider } from "./components/public/ComponentProvider"; import { MetabotQuestion } from "./components/public/MetabotQuestion"; +import { SdkDebugInfo } from "./components/public/SdkDebugInfo"; import { StaticQuestion } from "./components/public/StaticQuestion"; import { EditableDashboard, InteractiveDashboard, StaticDashboard, } from "./components/public/dashboard"; -import { SdkDebugInfo } from "./components/public/SdkDebugInfo"; -import { getApplicationName } from "metabase/selectors/whitelabel"; -import { getSdkStore } from "./store/index"; -import { - getAvailableFonts, - getLoginStatus, -} from "embedding-sdk-bundle/store/selectors"; -import { getUser } from "metabase/current-user"; import { useInitData } from "./hooks/private/use-init-data"; -import { useLogVersionInfo } from "embedding-sdk-bundle/hooks/private/use-log-version-info"; -import { createDashboard } from "embedding-sdk-bundle/lib/create-dashboard"; -import { executeAction } from "embedding-sdk-bundle/lib/execute-action"; -import { resolveDatasetQuery } from "embedding-sdk-bundle/lib/create-metabase-query"; -import { queryDataset } from "embedding-sdk-bundle/lib/query-dataset"; -import { queryQuestion } from "embedding-sdk-bundle/lib/query-question"; -import { validateFunctionSchema } from "embedding-sdk-bundle/lib/validate-function-schema"; -import { DateRangeCalendar } from "embedding-sdk-bundle/components/public/DateRangeCalendar/DateRangeCalendar"; -import { - formatDate, - formatDateRange, -} from "embedding-sdk-bundle/lib/format-date-range"; -import { DateRangePopover } from "embedding-sdk-bundle/components/public/DateRangePopover/DateRangePopover"; +import { getSdkStore } from "./store/index"; +import type { MetabaseEmbeddingSdkBundleExports } from "./types/sdk-bundle"; /** * IMPORTANT! diff --git a/frontend/src/embedding-sdk-bundle/store/guest-embed/auth.ts b/frontend/src/embedding-sdk-bundle/store/guest-embed/auth.ts index 9be48ef82b4..9da015b5b88 100644 --- a/frontend/src/embedding-sdk-bundle/store/guest-embed/auth.ts +++ b/frontend/src/embedding-sdk-bundle/store/guest-embed/auth.ts @@ -4,8 +4,8 @@ import { createAction } from "@reduxjs/toolkit"; import type { SdkStoreState } from "embedding-sdk-bundle/store/types"; import type { MetabaseAuthConfig } from "embedding-sdk-shared/types/auth-config"; -import { requestSessionTokenFromEmbedJs } from "metabase/embedding/embedding-iframe-sdk/utils/request-session-token"; import { isEmbeddingEajs } from "metabase/embedding-sdk/config"; +import { requestSessionTokenFromEmbedJs } from "metabase/embedding/embedding-iframe-sdk/utils/request-session-token"; import { createAsyncThunk } from "metabase/redux/utils"; import { decodeJwt } from "metabase/utils/jwt"; diff --git a/frontend/src/embedding-sdk-bundle/test/CommonSdkCorsStoryWrapper.tsx b/frontend/src/embedding-sdk-bundle/test/CommonSdkCorsStoryWrapper.tsx index 65707c3e6ab..cd7ae2809d5 100644 --- a/frontend/src/embedding-sdk-bundle/test/CommonSdkCorsStoryWrapper.tsx +++ b/frontend/src/embedding-sdk-bundle/test/CommonSdkCorsStoryWrapper.tsx @@ -2,7 +2,6 @@ import type { StoryFn } from "@storybook/react"; // To run initialization side effects like Mantine styles, dayjs plugins, etc import "embedding-sdk-bundle"; - import { ComponentProvider } from "embedding-sdk-bundle/components/public/ComponentProvider"; import type { MetabaseAuthConfig } from "embedding-sdk-bundle/types"; import { STORYBOOK_METABASE_INSTANCE_URL } from "embedding-sdk-shared/test/storybook-constants"; diff --git a/frontend/src/embedding-sdk-bundle/test/CommonSdkStoryWrapper.tsx b/frontend/src/embedding-sdk-bundle/test/CommonSdkStoryWrapper.tsx index 2cefa47b667..3c67cb33d6d 100644 --- a/frontend/src/embedding-sdk-bundle/test/CommonSdkStoryWrapper.tsx +++ b/frontend/src/embedding-sdk-bundle/test/CommonSdkStoryWrapper.tsx @@ -4,7 +4,6 @@ import { useMemo } from "react"; // To run initialization side effects like Mantine styles, dayjs plugins, etc import "embedding-sdk-bundle"; - import { ComponentProvider } from "embedding-sdk-bundle/components/public/ComponentProvider"; import { STORYBOOK_METABASE_INSTANCE_URL } from "embedding-sdk-shared/test/storybook-constants"; import { storybookThemes } from "embedding-sdk-shared/test/storybook-themes"; diff --git a/frontend/src/embedding-sdk-bundle/test/__support__/ui.tsx b/frontend/src/embedding-sdk-bundle/test/__support__/ui.tsx index 7b5c1beee3d..b3d9d506aea 100644 --- a/frontend/src/embedding-sdk-bundle/test/__support__/ui.tsx +++ b/frontend/src/embedding-sdk-bundle/test/__support__/ui.tsx @@ -2,8 +2,11 @@ import { render } from "@testing-library/react"; import _ from "underscore"; import { getStore } from "__support__/entities-store"; -import { seedApiQueryCache } from "__support__/state"; -import { type StoreSeedState, createMockState } from "__support__/state"; +import { + type StoreSeedState, + createMockState, + seedApiQueryCache, +} from "__support__/state"; import { ComponentProviderInternal } from "embedding-sdk-bundle/components/public/ComponentProvider"; import { sdkReducers } from "embedding-sdk-bundle/store"; import type { SdkStore } from "embedding-sdk-bundle/store/types"; diff --git a/frontend/src/metabase-lib/query/metadata.ts b/frontend/src/metabase-lib/query/metadata.ts index 4d2da9651ce..d06eea6d65c 100644 --- a/frontend/src/metabase-lib/query/metadata.ts +++ b/frontend/src/metabase-lib/query/metadata.ts @@ -87,7 +87,6 @@ export function metadataProvider( return ML.metadataProvider(databaseId, metadata); } -// eslint-disable-next-line @typescript-eslint/no-unused-vars -- used for types declare function DisplayInfoFn( query: Query, stageIndex: number, diff --git a/frontend/src/metabase-lib/query/types.ts b/frontend/src/metabase-lib/query/types.ts index 2368a75858f..7482270a451 100644 --- a/frontend/src/metabase-lib/query/types.ts +++ b/frontend/src/metabase-lib/query/types.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars -- used for types */ import type { CardId, DatabaseId, diff --git a/frontend/src/metabase-lib/v1/metadata/Field.ts b/frontend/src/metabase-lib/v1/metadata/Field.ts index d21949d05fe..bb67944f5d4 100644 --- a/frontend/src/metabase-lib/v1/metadata/Field.ts +++ b/frontend/src/metabase-lib/v1/metadata/Field.ts @@ -58,7 +58,7 @@ export type HydratedFieldDimension = Omit< // so merging an interface with the class is how instances inherit the API // field's properties without re-declaring them. The class declares the rest. // -// eslint-disable-next-line @typescript-eslint/no-empty-object-type +// eslint-disable-next-line typescript-js/no-empty-object-type interface Field extends Omit< ApiField, "table" | "target" | "name_field" | "fingerprint" | "dimensions" diff --git a/frontend/src/metabase-types/api/query.ts b/frontend/src/metabase-types/api/query.ts index 4f0e30ad4ea..7c65106edf1 100644 --- a/frontend/src/metabase-types/api/query.ts +++ b/frontend/src/metabase-types/api/query.ts @@ -46,7 +46,6 @@ export interface InternalDatasetQuery { offset?: number; } -// eslint-disable-next-line @typescript-eslint/no-unused-vars -- used for types declare const OpaqueDatasetQuerySymbol: unique symbol; export type OpaqueDatasetQuery = unknown & { // TODO (AlexP 10/09/25) -- replace usages of this field with Lib.databaseID and drop it from here diff --git a/frontend/src/metabase-types/api/settings.ts b/frontend/src/metabase-types/api/settings.ts index 5b7313ae65b..15d79ad09a5 100644 --- a/frontend/src/metabase-types/api/settings.ts +++ b/frontend/src/metabase-types/api/settings.ts @@ -250,7 +250,6 @@ export type GdrivePayload = { error?: string; }; -/* eslint-disable-next-line @typescript-eslint/no-unused-vars -- used for types */ const tokenStatusFeatures = [ "advanced-config", "advanced-permissions", diff --git a/frontend/src/metabase/App.styled.tsx b/frontend/src/metabase/App.styled.tsx index edf40f5450c..b7a33be13fb 100644 --- a/frontend/src/metabase/App.styled.tsx +++ b/frontend/src/metabase/App.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { APP_BAR_HEIGHT } from "metabase/nav/constants"; diff --git a/frontend/src/metabase/AppThemeProvider.tsx b/frontend/src/metabase/AppThemeProvider.tsx index d61649a0911..930226f8102 100644 --- a/frontend/src/metabase/AppThemeProvider.tsx +++ b/frontend/src/metabase/AppThemeProvider.tsx @@ -1,11 +1,11 @@ import { type ReactNode, useCallback, useEffect, useState } from "react"; +import { isEmbeddingSdk } from "metabase/embedding-sdk/config"; import { isPublicEmbedding, isStaticEmbedding, } from "metabase/embedding/config"; import type { DisplayTheme } from "metabase/embedding/types"; -import { isEmbeddingSdk } from "metabase/embedding-sdk/config"; import { useUpdateSettingMutation } from "metabase/settings"; import type { MantineThemeOverride } from "metabase/ui"; import { mutateColors } from "metabase/ui/colors/colors"; diff --git a/frontend/src/metabase/actions/containers/ActionCreator/ActionContext/ImplicitActionContextProvider/ImplicitActionContextProvider.styled.tsx b/frontend/src/metabase/actions/containers/ActionCreator/ActionContext/ImplicitActionContextProvider/ImplicitActionContextProvider.styled.tsx index c0e59338784..26270c04c3e 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/ActionContext/ImplicitActionContextProvider/ImplicitActionContextProvider.styled.tsx +++ b/frontend/src/metabase/actions/containers/ActionCreator/ActionContext/ImplicitActionContextProvider/ImplicitActionContextProvider.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const EditorBodyRoot = styled.div` diff --git a/frontend/src/metabase/actions/containers/ActionCreator/ActionCreator.styled.tsx b/frontend/src/metabase/actions/containers/ActionCreator/ActionCreator.styled.tsx index 9b659a04537..5816d782190 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/ActionCreator.styled.tsx +++ b/frontend/src/metabase/actions/containers/ActionCreator/ActionCreator.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import SidebarContentS from "metabase/common/components/SidebarContent/SidebarContent.module.css"; diff --git a/frontend/src/metabase/actions/containers/ActionCreator/ActionCreatorHeader.styled.tsx b/frontend/src/metabase/actions/containers/ActionCreator/ActionCreatorHeader.styled.tsx index f247ec07a4c..d410aab86fb 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/ActionCreatorHeader.styled.tsx +++ b/frontend/src/metabase/actions/containers/ActionCreator/ActionCreatorHeader.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { EditableText as EditableTextBase } from "metabase/common/components/EditableText"; diff --git a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/Description/Description.styled.tsx b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/Description/Description.styled.tsx index 75946ebf40a..b8914bc7133 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/Description/Description.styled.tsx +++ b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/Description/Description.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const InfoText = styled.div` diff --git a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/EmptyFormPlaceholder/EmptyFormPlaceholder.styled.tsx b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/EmptyFormPlaceholder/EmptyFormPlaceholder.styled.tsx index 2300772b0be..870482cf572 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/EmptyFormPlaceholder/EmptyFormPlaceholder.styled.tsx +++ b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/EmptyFormPlaceholder/EmptyFormPlaceholder.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { ExternalLink } from "metabase/common/components/ExternalLink"; diff --git a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FieldSettingsPopover.styled.tsx b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FieldSettingsPopover.styled.tsx index 5231697b905..80a998ac2e9 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FieldSettingsPopover.styled.tsx +++ b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FieldSettingsPopover.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Icon } from "metabase/ui"; diff --git a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FormCreator.styled.tsx b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FormCreator.styled.tsx index 52f6d5556a7..46bc8486895 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FormCreator.styled.tsx +++ b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FormCreator.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { alpha } from "metabase/ui/colors"; diff --git a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FormFieldEditor/FormFieldEditor.styled.tsx b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FormFieldEditor/FormFieldEditor.styled.tsx index 1fa3f6549a5..09f4882b98d 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FormFieldEditor/FormFieldEditor.styled.tsx +++ b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FormFieldEditor/FormFieldEditor.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { FormField } from "metabase/common/components/FormField"; diff --git a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/OptionEditor.styled.tsx b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/OptionEditor.styled.tsx index 318e8dca024..6d8fe2185a2 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/OptionEditor.styled.tsx +++ b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/OptionEditor.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const OptionEditorContainer = styled.div` diff --git a/frontend/src/metabase/actions/containers/ActionCreator/InlineActionSettings.styled.tsx b/frontend/src/metabase/actions/containers/ActionCreator/InlineActionSettings.styled.tsx index f5c3ded3214..73561d46d2a 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/InlineActionSettings.styled.tsx +++ b/frontend/src/metabase/actions/containers/ActionCreator/InlineActionSettings.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const ActionSettingsContent = styled.div` diff --git a/frontend/src/metabase/admin/embedding/components/ThemeEditor/ResourcePreview.tsx b/frontend/src/metabase/admin/embedding/components/ThemeEditor/ResourcePreview.tsx index 1d38ce04348..107a7766d50 100644 --- a/frontend/src/metabase/admin/embedding/components/ThemeEditor/ResourcePreview.tsx +++ b/frontend/src/metabase/admin/embedding/components/ThemeEditor/ResourcePreview.tsx @@ -8,10 +8,10 @@ import { } from "react"; import { match } from "ts-pattern"; +import type { MetabaseTheme } from "metabase/embedding-sdk/theme"; import { METABASE_CONFIG_IS_PROXY_FIELD_NAME } from "metabase/embedding/embedding-iframe-sdk/constants"; import { setupConfigWatcher } from "metabase/embedding/embedding-iframe-sdk/embed"; import type { SdkIframeEmbedBaseSettings } from "metabase/embedding/embedding-iframe-sdk/types/embed"; -import type { MetabaseTheme } from "metabase/embedding-sdk/theme"; import { useSetting } from "metabase/settings"; import { Box, Center, Loader } from "metabase/ui"; diff --git a/frontend/src/metabase/admin/permissions/components/FilterableTree/FilterableTree.styled.tsx b/frontend/src/metabase/admin/permissions/components/FilterableTree/FilterableTree.styled.tsx index 26c51bc4fd2..af21c364fe2 100644 --- a/frontend/src/metabase/admin/permissions/components/FilterableTree/FilterableTree.styled.tsx +++ b/frontend/src/metabase/admin/permissions/components/FilterableTree/FilterableTree.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Tree } from "metabase/common/components/tree"; diff --git a/frontend/src/metabase/admin/permissions/components/PermissionHelpDescription/PermissionHelpDescription.styled.tsx b/frontend/src/metabase/admin/permissions/components/PermissionHelpDescription/PermissionHelpDescription.styled.tsx index 9d4c6115b43..714520d04fd 100644 --- a/frontend/src/metabase/admin/permissions/components/PermissionHelpDescription/PermissionHelpDescription.styled.tsx +++ b/frontend/src/metabase/admin/permissions/components/PermissionHelpDescription/PermissionHelpDescription.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import type { ColorName } from "metabase/ui/colors/types"; diff --git a/frontend/src/metabase/admin/permissions/components/PermissionsEditor/PermissionsEditorBreadcrumbs.styled.tsx b/frontend/src/metabase/admin/permissions/components/PermissionsEditor/PermissionsEditorBreadcrumbs.styled.tsx index f70ba5bd8da..74a95e94cbe 100644 --- a/frontend/src/metabase/admin/permissions/components/PermissionsEditor/PermissionsEditorBreadcrumbs.styled.tsx +++ b/frontend/src/metabase/admin/permissions/components/PermissionsEditor/PermissionsEditorBreadcrumbs.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { lighten } from "metabase/ui/colors"; diff --git a/frontend/src/metabase/admin/permissions/components/PermissionsEditor/PermissionsEditorContent.styled.tsx b/frontend/src/metabase/admin/permissions/components/PermissionsEditor/PermissionsEditorContent.styled.tsx index 61804f70831..444a6d3bf9b 100644 --- a/frontend/src/metabase/admin/permissions/components/PermissionsEditor/PermissionsEditorContent.styled.tsx +++ b/frontend/src/metabase/admin/permissions/components/PermissionsEditor/PermissionsEditorContent.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const PermissionEditorContentRoot = styled.div` diff --git a/frontend/src/metabase/admin/permissions/components/PermissionsPageLayout/PermissionsPageLayout.styled.tsx b/frontend/src/metabase/admin/permissions/components/PermissionsPageLayout/PermissionsPageLayout.styled.tsx index f32fd6df93c..948247631be 100644 --- a/frontend/src/metabase/admin/permissions/components/PermissionsPageLayout/PermissionsPageLayout.styled.tsx +++ b/frontend/src/metabase/admin/permissions/components/PermissionsPageLayout/PermissionsPageLayout.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { forwardRef } from "react"; diff --git a/frontend/src/metabase/admin/permissions/components/PermissionsSelect/PermissionsSelect.styled.tsx b/frontend/src/metabase/admin/permissions/components/PermissionsSelect/PermissionsSelect.styled.tsx index cd4f736027e..355de7fb33e 100644 --- a/frontend/src/metabase/admin/permissions/components/PermissionsSelect/PermissionsSelect.styled.tsx +++ b/frontend/src/metabase/admin/permissions/components/PermissionsSelect/PermissionsSelect.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { forwardRef } from "react"; diff --git a/frontend/src/metabase/admin/permissions/components/PermissionsSelect/PermissionsSelectOption.styled.tsx b/frontend/src/metabase/admin/permissions/components/PermissionsSelect/PermissionsSelectOption.styled.tsx index 9eb24a4a825..1c2c21d7440 100644 --- a/frontend/src/metabase/admin/permissions/components/PermissionsSelect/PermissionsSelectOption.styled.tsx +++ b/frontend/src/metabase/admin/permissions/components/PermissionsSelect/PermissionsSelectOption.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import type { ColorName } from "metabase/ui/colors/types"; diff --git a/frontend/src/metabase/admin/permissions/components/PermissionsSidebar/PermissionsSidebar.styled.tsx b/frontend/src/metabase/admin/permissions/components/PermissionsSidebar/PermissionsSidebar.styled.tsx index 9dc8fbc028c..75a7b4cfc8d 100644 --- a/frontend/src/metabase/admin/permissions/components/PermissionsSidebar/PermissionsSidebar.styled.tsx +++ b/frontend/src/metabase/admin/permissions/components/PermissionsSidebar/PermissionsSidebar.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Icon } from "metabase/ui"; diff --git a/frontend/src/metabase/admin/permissions/components/PermissionsTable/PermissionsTable.styled.tsx b/frontend/src/metabase/admin/permissions/components/PermissionsTable/PermissionsTable.styled.tsx index bcc3e0cdc61..203cec8bc24 100644 --- a/frontend/src/metabase/admin/permissions/components/PermissionsTable/PermissionsTable.styled.tsx +++ b/frontend/src/metabase/admin/permissions/components/PermissionsTable/PermissionsTable.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { forwardRef } from "react"; diff --git a/frontend/src/metabase/admin/permissions/components/ToolbarButton/ToolbarButton.styled.tsx b/frontend/src/metabase/admin/permissions/components/ToolbarButton/ToolbarButton.styled.tsx index f4db2dfbe7a..1672638e4fe 100644 --- a/frontend/src/metabase/admin/permissions/components/ToolbarButton/ToolbarButton.styled.tsx +++ b/frontend/src/metabase/admin/permissions/components/ToolbarButton/ToolbarButton.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Icon } from "metabase/ui"; diff --git a/frontend/src/metabase/admin/settings/components/CloudPanel/CloudPanel.tsx b/frontend/src/metabase/admin/settings/components/CloudPanel/CloudPanel.tsx index a94acc21c42..75dfbbf1cb4 100644 --- a/frontend/src/metabase/admin/settings/components/CloudPanel/CloudPanel.tsx +++ b/frontend/src/metabase/admin/settings/components/CloudPanel/CloudPanel.tsx @@ -3,8 +3,12 @@ import { useEffect, useState } from "react"; import { LoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErrorWrapper"; import { useStoreUrl } from "metabase/common/hooks"; import { useSelector } from "metabase/redux"; -import { getSetting, useLazyGetSettingsQuery } from "metabase/settings"; -import { type Plan, getPlan } from "metabase/settings"; +import { + type Plan, + getPlan, + getSetting, + useLazyGetSettingsQuery, +} from "metabase/settings"; import { Box } from "metabase/ui"; import type { CloudMigration } from "metabase-types/api/cloud-migration"; diff --git a/frontend/src/metabase/api/entity-id.ts b/frontend/src/metabase/api/entity-id.ts index 8f76dbae28e..ad23dd0353d 100644 --- a/frontend/src/metabase/api/entity-id.ts +++ b/frontend/src/metabase/api/entity-id.ts @@ -5,7 +5,6 @@ import { import { Api } from "./api"; -/* eslint-disable-next-line @typescript-eslint/no-unused-vars -- used for types */ const validEntityTypes = [ "action", "card", diff --git a/frontend/src/metabase/app-embed-mcp.tsx b/frontend/src/metabase/app-embed-mcp.tsx index 81f312bf9ea..7b762274423 100644 --- a/frontend/src/metabase/app-embed-mcp.tsx +++ b/frontend/src/metabase/app-embed-mcp.tsx @@ -1,16 +1,13 @@ // Must run before any dynamic import(): sets webpack's runtime publicPath so // on-demand chunks (leaflet, echarts) resolve to the Metabase instance. import "./app-embed-mcp-public-path"; - import { createRoot } from "react-dom/client"; // Import the embedding SDK vendors side-effects (sets up global CSS vars, etc.) import "metabase/embedding-sdk/vendors-side-effects"; - -import { McpUiAppRoute } from "metabase/embedding/mcp/McpUiAppRoute"; import { EMBEDDING_SDK_CONFIG } from "metabase/embedding-sdk/config"; +import { McpUiAppRoute } from "metabase/embedding/mcp/McpUiAppRoute"; import { setBasename } from "metabase/utils/basename"; - // Load EE plugins (whitelabeling, etc.) - no-op in OSS import "sdk-iframe-embedding-ee-plugins"; diff --git a/frontend/src/metabase/app-embed-sdk.tsx b/frontend/src/metabase/app-embed-sdk.tsx index c66ebaa5e69..2b0f6ca199b 100644 --- a/frontend/src/metabase/app-embed-sdk.tsx +++ b/frontend/src/metabase/app-embed-sdk.tsx @@ -1,11 +1,14 @@ +// oxfmt-ignore import { createRoot } from "react-dom/client"; // Setup CSP nonce for CodeMirror dynamic styles +// oxfmt-ignore import "metabase/utils/csp-setup"; // Import the embedding SDK vendors side-effects +// oxfmt-ignore import "metabase/embedding-sdk/vendors-side-effects"; -// eslint-disable-next-line import/order +// oxfmt-ignore import { EMBEDDING_SDK_CONFIG, EMBEDDING_SDK_IFRAME_EMBEDDING_CONFIG, @@ -21,10 +24,11 @@ EMBEDDING_SDK_CONFIG.enableEmbeddingSettingKey = "enable-embedding-simple"; EMBEDDING_SDK_CONFIG.tokenFeatureKey = "embedding_simple"; // load the embedding_iframe_sdk EE plugin +// oxfmt-ignore import "sdk-iframe-embedding-ee-plugins"; // Must be imported after the EE plugins are loaded -// eslint-disable-next-line import/order +// oxfmt-ignore import { SdkIframeEmbedRoute } from "metabase/embedding/embedding-iframe-sdk/components/SdkIframeEmbedRoute"; function _init() { diff --git a/frontend/src/metabase/app-main.ts b/frontend/src/metabase/app-main.ts index bda009a9074..6072e75af2f 100644 --- a/frontend/src/metabase/app-main.ts +++ b/frontend/src/metabase/app-main.ts @@ -1,7 +1,6 @@ // Enables hot reload in development and noop in production // MUST be imported BEFORE `react` and `react-dom` import "metabase-dev"; - import { Api } from "metabase/api"; import { PLUGIN_API, api } from "metabase/api/client"; import { init } from "metabase/app"; diff --git a/frontend/src/metabase/app-static-viz.ts b/frontend/src/metabase/app-static-viz.ts index 78db8279741..07f0d1b0525 100644 --- a/frontend/src/metabase/app-static-viz.ts +++ b/frontend/src/metabase/app-static-viz.ts @@ -1,5 +1,4 @@ import "metabase/static-viz/polyfill"; - import { getCellBackgroundColors, renderChart } from "metabase/static-viz"; export function renderChartJSON(inputJSON: string): string { diff --git a/frontend/src/metabase/app.tsx b/frontend/src/metabase/app.tsx index 2599682efa9..3c7817a3ef6 100644 --- a/frontend/src/metabase/app.tsx +++ b/frontend/src/metabase/app.tsx @@ -1,25 +1,19 @@ import "@mantine/core/styles.css"; import "@mantine/dates/styles.css"; - // This is conditionally aliased in the webpack config. // If EE isn't enabled, it loads an empty file. // Should be imported before any other metabase import import "ee-overrides"; - // set the locale before loading anything else import "metabase/utils/i18n"; - // NOTE: why do we need to load this here? import "metabase/ui/colors"; - // NOTE: this loads all builtin plugins import "metabase/auth/plugins"; - // This is conditionally aliased in the webpack config. // If EE isn't enabled, it loads an empty file. // Set CSP nonce for dynamic style injection (e.g. CodeMirror) import "metabase/utils/csp-setup"; - import { type Middleware, isAction } from "@reduxjs/toolkit"; import { useLayoutEffect } from "react"; import { DragDropContextProvider } from "react-dnd"; diff --git a/frontend/src/metabase/auth/components/AuthLayout/AuthLayout.styled.tsx b/frontend/src/metabase/auth/components/AuthLayout/AuthLayout.styled.tsx index 0c00e1449bd..f89cc64f932 100644 --- a/frontend/src/metabase/auth/components/AuthLayout/AuthLayout.styled.tsx +++ b/frontend/src/metabase/auth/components/AuthLayout/AuthLayout.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { breakpointMinSmall } from "metabase/styled-components/theme"; diff --git a/frontend/src/metabase/auth/components/ForgotPassword/ForgotPassword.styled.tsx b/frontend/src/metabase/auth/components/ForgotPassword/ForgotPassword.styled.tsx index 194413867f8..a165ca490be 100644 --- a/frontend/src/metabase/auth/components/ForgotPassword/ForgotPassword.styled.tsx +++ b/frontend/src/metabase/auth/components/ForgotPassword/ForgotPassword.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Link } from "metabase/common/components/Link"; diff --git a/frontend/src/metabase/auth/components/ForgotPasswordForm/ForgotPasswordForm.styled.tsx b/frontend/src/metabase/auth/components/ForgotPasswordForm/ForgotPasswordForm.styled.tsx index 504f2c3b0c4..81a7d64904f 100644 --- a/frontend/src/metabase/auth/components/ForgotPasswordForm/ForgotPasswordForm.styled.tsx +++ b/frontend/src/metabase/auth/components/ForgotPasswordForm/ForgotPasswordForm.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Link } from "metabase/common/components/Link/Link"; diff --git a/frontend/src/metabase/auth/components/PasswordPanel/PasswordPanel.styled.tsx b/frontend/src/metabase/auth/components/PasswordPanel/PasswordPanel.styled.tsx index 7f91e85edd2..5088835191d 100644 --- a/frontend/src/metabase/auth/components/PasswordPanel/PasswordPanel.styled.tsx +++ b/frontend/src/metabase/auth/components/PasswordPanel/PasswordPanel.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const ActionList = styled.div` diff --git a/frontend/src/metabase/auth/components/ResetPassword/ResetPassword.styled.tsx b/frontend/src/metabase/auth/components/ResetPassword/ResetPassword.styled.tsx index 954c806422f..8e4e285dac7 100644 --- a/frontend/src/metabase/auth/components/ResetPassword/ResetPassword.styled.tsx +++ b/frontend/src/metabase/auth/components/ResetPassword/ResetPassword.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const InfoBody = styled.div` diff --git a/frontend/src/metabase/auth/components/ResetPasswordForm/ResetPasswordForm.styled.tsx b/frontend/src/metabase/auth/components/ResetPasswordForm/ResetPasswordForm.styled.tsx index 7cb201049d1..13d7fb1c4ac 100644 --- a/frontend/src/metabase/auth/components/ResetPasswordForm/ResetPasswordForm.styled.tsx +++ b/frontend/src/metabase/auth/components/ResetPasswordForm/ResetPasswordForm.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const PasswordFormTitle = styled.div` diff --git a/frontend/src/metabase/common/components/BulkActionBar/BulkActionBar.styled.tsx b/frontend/src/metabase/common/components/BulkActionBar/BulkActionBar.styled.tsx index bdc28b34795..f84367f45f2 100644 --- a/frontend/src/metabase/common/components/BulkActionBar/BulkActionBar.styled.tsx +++ b/frontend/src/metabase/common/components/BulkActionBar/BulkActionBar.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { NAV_SIDEBAR_WIDTH } from "metabase/nav/constants"; diff --git a/frontend/src/metabase/common/components/DashboardSelector/DashboardSelector.styled.tsx b/frontend/src/metabase/common/components/DashboardSelector/DashboardSelector.styled.tsx index b684fccfbd4..52aa2998b48 100644 --- a/frontend/src/metabase/common/components/DashboardSelector/DashboardSelector.styled.tsx +++ b/frontend/src/metabase/common/components/DashboardSelector/DashboardSelector.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { SelectButton } from "metabase/common/components/SelectButton/SelectButton"; diff --git a/frontend/src/metabase/common/components/EditBar/EditBar.styled.tsx b/frontend/src/metabase/common/components/EditBar/EditBar.styled.tsx index 8ae19d67b96..df0987b262b 100644 --- a/frontend/src/metabase/common/components/EditBar/EditBar.styled.tsx +++ b/frontend/src/metabase/common/components/EditBar/EditBar.styled.tsx @@ -1,5 +1,5 @@ import isPropValid from "@emotion/is-prop-valid"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { FullWidthContainer } from "metabase/styled-components/layout/FullWidthContainer"; diff --git a/frontend/src/metabase/common/components/EditableText/EditableText.styled.tsx b/frontend/src/metabase/common/components/EditableText/EditableText.styled.tsx index d690d4d8c90..8e010882393 100644 --- a/frontend/src/metabase/common/components/EditableText/EditableText.styled.tsx +++ b/frontend/src/metabase/common/components/EditableText/EditableText.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; interface EditableTextRootProps { diff --git a/frontend/src/metabase/common/components/ExplicitSize/ExplicitSize.tsx b/frontend/src/metabase/common/components/ExplicitSize/ExplicitSize.tsx index 0eba8c266bd..9ce5dc0a35d 100644 --- a/frontend/src/metabase/common/components/ExplicitSize/ExplicitSize.tsx +++ b/frontend/src/metabase/common/components/ExplicitSize/ExplicitSize.tsx @@ -56,6 +56,7 @@ type ExplicitSizeOuterProps = Omit; /** * @deprecated HOCs are deprecated */ +// eslint-disable-next-line react/display-name -- HOC names the wrapped component below. export function ExplicitSize({ selector, wrapped = false, diff --git a/frontend/src/metabase/common/components/FileInput/FileInput.styled.tsx b/frontend/src/metabase/common/components/FileInput/FileInput.styled.tsx index 30713e321e9..f58bdeccf48 100644 --- a/frontend/src/metabase/common/components/FileInput/FileInput.styled.tsx +++ b/frontend/src/metabase/common/components/FileInput/FileInput.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const InputRoot = styled.label` diff --git a/frontend/src/metabase/common/components/FormField/FormField.styled.tsx b/frontend/src/metabase/common/components/FormField/FormField.styled.tsx index 4d359380911..ea09a600a5e 100644 --- a/frontend/src/metabase/common/components/FormField/FormField.styled.tsx +++ b/frontend/src/metabase/common/components/FormField/FormField.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Icon } from "metabase/ui"; diff --git a/frontend/src/metabase/common/components/InfoText/InfoTextEditedInfo.styled.tsx b/frontend/src/metabase/common/components/InfoText/InfoTextEditedInfo.styled.tsx index d343f232634..239aded4b29 100644 --- a/frontend/src/metabase/common/components/InfoText/InfoTextEditedInfo.styled.tsx +++ b/frontend/src/metabase/common/components/InfoText/InfoTextEditedInfo.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { LastEditInfoLabel } from "metabase/common/components/LastEditInfoLabel"; diff --git a/frontend/src/metabase/common/components/ItemsTable/BaseItemsTable.styled.tsx b/frontend/src/metabase/common/components/ItemsTable/BaseItemsTable.styled.tsx index b6e606ff8d5..71253656b63 100644 --- a/frontend/src/metabase/common/components/ItemsTable/BaseItemsTable.styled.tsx +++ b/frontend/src/metabase/common/components/ItemsTable/BaseItemsTable.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import cx from "classnames"; import { diff --git a/frontend/src/metabase/common/components/ItemsTable/Columns.tsx b/frontend/src/metabase/common/components/ItemsTable/Columns.tsx index 0c30ffcd665..6dfe45af457 100644 --- a/frontend/src/metabase/common/components/ItemsTable/Columns.tsx +++ b/frontend/src/metabase/common/components/ItemsTable/Columns.tsx @@ -7,8 +7,8 @@ import { DateTime } from "metabase/common/components/DateTime"; import { EntityItemName } from "metabase/common/components/EntityItemName"; import { Markdown } from "metabase/common/components/Markdown"; import { useTranslateContent } from "metabase/content-translation/hooks"; -import { ArchiveButton } from "metabase/embedding/components/ArchiveButton"; import { isEmbeddingSdk } from "metabase/embedding-sdk/config"; +import { ArchiveButton } from "metabase/embedding/components/ArchiveButton"; import { PLUGIN_MODERATION } from "metabase/plugins"; import { Checkbox, Ellipsified, type IconProps, Tooltip } from "metabase/ui"; import { modelToUrl } from "metabase/urls"; @@ -21,8 +21,6 @@ import type { SearchResult, } from "metabase-types/api"; -import type { SortableColumnHeaderProps } from "./BaseItemsTable/BaseItemsTable"; -import { SortableColumnHeader } from "./BaseItemsTable/BaseItemsTable"; import { BulkSelectWrapper, ColumnHeader, @@ -34,6 +32,8 @@ import { RowActionsContainer, TableColumn, } from "./BaseItemsTable.styled"; +import type { SortableColumnHeaderProps } from "./BaseItemsTable/BaseItemsTable"; +import { SortableColumnHeader } from "./BaseItemsTable/BaseItemsTable"; import { EntityIconCheckBox } from "./EntityIconCheckBox"; type HeaderProps = Omit< diff --git a/frontend/src/metabase/common/components/ItemsTable/ItemsTable.styled.tsx b/frontend/src/metabase/common/components/ItemsTable/ItemsTable.styled.tsx index 80c4c67d75e..2670be2f330 100644 --- a/frontend/src/metabase/common/components/ItemsTable/ItemsTable.styled.tsx +++ b/frontend/src/metabase/common/components/ItemsTable/ItemsTable.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const ItemsTableRoot = styled.div` diff --git a/frontend/src/metabase/common/components/MetadataInfo/InfoIcon/InfoIcon.styled.tsx b/frontend/src/metabase/common/components/MetadataInfo/InfoIcon/InfoIcon.styled.tsx index b7323e79481..6bbdbd6493b 100644 --- a/frontend/src/metabase/common/components/MetadataInfo/InfoIcon/InfoIcon.styled.tsx +++ b/frontend/src/metabase/common/components/MetadataInfo/InfoIcon/InfoIcon.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Icon } from "metabase/ui"; diff --git a/frontend/src/metabase/common/components/MetadataInfo/MetadataInfo.styled.tsx b/frontend/src/metabase/common/components/MetadataInfo/MetadataInfo.styled.tsx index 886505c0123..9528205820d 100644 --- a/frontend/src/metabase/common/components/MetadataInfo/MetadataInfo.styled.tsx +++ b/frontend/src/metabase/common/components/MetadataInfo/MetadataInfo.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Icon } from "metabase/ui"; diff --git a/frontend/src/metabase/common/components/MetadataInfo/Popover/Popover.styled.tsx b/frontend/src/metabase/common/components/MetadataInfo/Popover/Popover.styled.tsx index c0367a76d7e..d699e200f95 100644 --- a/frontend/src/metabase/common/components/MetadataInfo/Popover/Popover.styled.tsx +++ b/frontend/src/metabase/common/components/MetadataInfo/Popover/Popover.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { HoverCard } from "metabase/ui"; diff --git a/frontend/src/metabase/common/components/MetadataInfo/QueryColumnFingerprintInfo/GlobalFingerprint.styled.tsx b/frontend/src/metabase/common/components/MetadataInfo/QueryColumnFingerprintInfo/GlobalFingerprint.styled.tsx index 31150baad1e..07f00f939e6 100644 --- a/frontend/src/metabase/common/components/MetadataInfo/QueryColumnFingerprintInfo/GlobalFingerprint.styled.tsx +++ b/frontend/src/metabase/common/components/MetadataInfo/QueryColumnFingerprintInfo/GlobalFingerprint.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { isReducedMotionPreferred } from "metabase/utils/dom"; diff --git a/frontend/src/metabase/common/components/MetadataInfo/QueryColumnInfo/QueryColumnInfo.styled.tsx b/frontend/src/metabase/common/components/MetadataInfo/QueryColumnInfo/QueryColumnInfo.styled.tsx index 1d9efa28c89..fd31b21b6e0 100644 --- a/frontend/src/metabase/common/components/MetadataInfo/QueryColumnInfo/QueryColumnInfo.styled.tsx +++ b/frontend/src/metabase/common/components/MetadataInfo/QueryColumnInfo/QueryColumnInfo.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { InfoContainer as _InfoContainer } from "metabase/common/components/MetadataInfo/MetadataInfo.styled"; diff --git a/frontend/src/metabase/common/components/MetadataInfo/TableInfo/ConnectedTables.styled.tsx b/frontend/src/metabase/common/components/MetadataInfo/TableInfo/ConnectedTables.styled.tsx index b967efba732..09d792f0741 100644 --- a/frontend/src/metabase/common/components/MetadataInfo/TableInfo/ConnectedTables.styled.tsx +++ b/frontend/src/metabase/common/components/MetadataInfo/TableInfo/ConnectedTables.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Link } from "metabase/common/components/Link"; diff --git a/frontend/src/metabase/common/components/MetadataInfo/TableInfo/TableInfo.styled.tsx b/frontend/src/metabase/common/components/MetadataInfo/TableInfo/TableInfo.styled.tsx index 058cb3ebec9..fb6bae434f0 100644 --- a/frontend/src/metabase/common/components/MetadataInfo/TableInfo/TableInfo.styled.tsx +++ b/frontend/src/metabase/common/components/MetadataInfo/TableInfo/TableInfo.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { diff --git a/frontend/src/metabase/common/components/MetadataInfo/TableLabel/TableLabel.styled.tsx b/frontend/src/metabase/common/components/MetadataInfo/TableLabel/TableLabel.styled.tsx index f580e88f918..2b1592092e5 100644 --- a/frontend/src/metabase/common/components/MetadataInfo/TableLabel/TableLabel.styled.tsx +++ b/frontend/src/metabase/common/components/MetadataInfo/TableLabel/TableLabel.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { RelativeSizeIcon } from "../MetadataInfo.styled"; diff --git a/frontend/src/metabase/common/components/SelectList/SelectListItem.styled.tsx b/frontend/src/metabase/common/components/SelectList/SelectListItem.styled.tsx index a8eb2dd9269..6fd512dbb0c 100644 --- a/frontend/src/metabase/common/components/SelectList/SelectListItem.styled.tsx +++ b/frontend/src/metabase/common/components/SelectList/SelectListItem.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Text, type TextProps } from "metabase/ui"; diff --git a/frontend/src/metabase/common/components/Swapper/Swapper.styled.tsx b/frontend/src/metabase/common/components/Swapper/Swapper.styled.tsx index 9091115858e..37b85fdd76b 100644 --- a/frontend/src/metabase/common/components/Swapper/Swapper.styled.tsx +++ b/frontend/src/metabase/common/components/Swapper/Swapper.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; interface SwapperElementProps { diff --git a/frontend/src/metabase/common/components/Tab/Tab.styled.tsx b/frontend/src/metabase/common/components/Tab/Tab.styled.tsx index a15ba4ff550..34cd6d65ef7 100644 --- a/frontend/src/metabase/common/components/Tab/Tab.styled.tsx +++ b/frontend/src/metabase/common/components/Tab/Tab.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { focusOutlineStyle } from "metabase/common/style/focus-outline"; diff --git a/frontend/src/metabase/common/components/TabButton/TabButton.styled.tsx b/frontend/src/metabase/common/components/TabButton/TabButton.styled.tsx index 577e598bc7e..3146f68c044 100644 --- a/frontend/src/metabase/common/components/TabButton/TabButton.styled.tsx +++ b/frontend/src/metabase/common/components/TabButton/TabButton.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; interface TabButtonProps { diff --git a/frontend/src/metabase/common/components/TabButton/TabButton.tsx b/frontend/src/metabase/common/components/TabButton/TabButton.tsx index 110db4acd10..7deca9a82a5 100644 --- a/frontend/src/metabase/common/components/TabButton/TabButton.tsx +++ b/frontend/src/metabase/common/components/TabButton/TabButton.tsx @@ -1,7 +1,7 @@ import type { UniqueIdentifier } from "@dnd-kit/core"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import cx from "classnames"; import type { diff --git a/frontend/src/metabase/common/components/Unsubscribe.styled.tsx b/frontend/src/metabase/common/components/Unsubscribe.styled.tsx index 3b2ada7d19d..31b2ac1b0bf 100644 --- a/frontend/src/metabase/common/components/Unsubscribe.styled.tsx +++ b/frontend/src/metabase/common/components/Unsubscribe.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { MetabotLogo } from "metabase/common/components/MetabotLogo"; diff --git a/frontend/src/metabase/common/components/UserAvatar/UserAvatar.styled.tsx b/frontend/src/metabase/common/components/UserAvatar/UserAvatar.styled.tsx index 4fcfbb49398..bc88c26bec4 100644 --- a/frontend/src/metabase/common/components/UserAvatar/UserAvatar.styled.tsx +++ b/frontend/src/metabase/common/components/UserAvatar/UserAvatar.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { color } from "metabase/ui/utils/colors"; diff --git a/frontend/src/metabase/common/components/tree/TreeNode.styled.tsx b/frontend/src/metabase/common/components/tree/TreeNode.styled.tsx index ab1c6e3b585..715edee7d45 100644 --- a/frontend/src/metabase/common/components/tree/TreeNode.styled.tsx +++ b/frontend/src/metabase/common/components/tree/TreeNode.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import type { IconProps } from "metabase/ui"; diff --git a/frontend/src/metabase/common/components/upload/UploadInput.tsx b/frontend/src/metabase/common/components/upload/UploadInput.tsx index 2ca672918a4..fcde5822d62 100644 --- a/frontend/src/metabase/common/components/upload/UploadInput.tsx +++ b/frontend/src/metabase/common/components/upload/UploadInput.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { type InputHTMLAttributes, forwardRef } from "react"; diff --git a/frontend/src/metabase/common/style/focus-outline.ts b/frontend/src/metabase/common/style/focus-outline.ts index c4449a03474..427f6d0a951 100644 --- a/frontend/src/metabase/common/style/focus-outline.ts +++ b/frontend/src/metabase/common/style/focus-outline.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; import { getFocusColor } from "metabase/ui/colors"; diff --git a/frontend/src/metabase/css/core/base.styled.ts b/frontend/src/metabase/css/core/base.styled.ts index b39cba46cb3..816647786ec 100644 --- a/frontend/src/metabase/css/core/base.styled.ts +++ b/frontend/src/metabase/css/core/base.styled.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; export const rootStyle = css` diff --git a/frontend/src/metabase/css/core/fonts.styled.ts b/frontend/src/metabase/css/core/fonts.styled.ts index 641640cea80..b39467edb09 100644 --- a/frontend/src/metabase/css/core/fonts.styled.ts +++ b/frontend/src/metabase/css/core/fonts.styled.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; export const defaultFontFiles = ({ baseUrl = "./" } = {}) => { diff --git a/frontend/src/metabase/dashboard/components/ActionViz/ActionButton.styled.tsx b/frontend/src/metabase/dashboard/components/ActionViz/ActionButton.styled.tsx index ec9697861fa..d8df671cafd 100644 --- a/frontend/src/metabase/dashboard/components/ActionViz/ActionButton.styled.tsx +++ b/frontend/src/metabase/dashboard/components/ActionViz/ActionButton.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const FullContainer = styled.div` diff --git a/frontend/src/metabase/dashboard/components/ActionViz/ActionDashcardSettings.styled.tsx b/frontend/src/metabase/dashboard/components/ActionViz/ActionDashcardSettings.styled.tsx index df7a36b4b01..b7bb0158bf1 100644 --- a/frontend/src/metabase/dashboard/components/ActionViz/ActionDashcardSettings.styled.tsx +++ b/frontend/src/metabase/dashboard/components/ActionViz/ActionDashcardSettings.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const ActionSettingsWrapper = styled.div` diff --git a/frontend/src/metabase/dashboard/components/ActionViz/ActionForm.styled.tsx b/frontend/src/metabase/dashboard/components/ActionViz/ActionForm.styled.tsx index dcf3bc96e19..565cc181edf 100644 --- a/frontend/src/metabase/dashboard/components/ActionViz/ActionForm.styled.tsx +++ b/frontend/src/metabase/dashboard/components/ActionViz/ActionForm.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const FormWrapper = styled.div` diff --git a/frontend/src/metabase/dashboard/components/ActionViz/ActionParameterMapping.styled.tsx b/frontend/src/metabase/dashboard/components/ActionViz/ActionParameterMapping.styled.tsx index c78a3d1cb9c..ab911dd4684 100644 --- a/frontend/src/metabase/dashboard/components/ActionViz/ActionParameterMapping.styled.tsx +++ b/frontend/src/metabase/dashboard/components/ActionViz/ActionParameterMapping.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { color } from "metabase/ui/utils/colors"; diff --git a/frontend/src/metabase/dashboard/components/ActionViz/ActionPicker/ActionPicker.styled.tsx b/frontend/src/metabase/dashboard/components/ActionViz/ActionPicker/ActionPicker.styled.tsx index 75c92af4747..f442162f2c3 100644 --- a/frontend/src/metabase/dashboard/components/ActionViz/ActionPicker/ActionPicker.styled.tsx +++ b/frontend/src/metabase/dashboard/components/ActionViz/ActionPicker/ActionPicker.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { CollapseSection } from "metabase/common/components/CollapseSection"; diff --git a/frontend/src/metabase/dashboard/components/ActionViz/ExplainerText/ExplainerText.styled.tsx b/frontend/src/metabase/dashboard/components/ActionViz/ExplainerText/ExplainerText.styled.tsx index a95b1e6f304..529a530d604 100644 --- a/frontend/src/metabase/dashboard/components/ActionViz/ExplainerText/ExplainerText.styled.tsx +++ b/frontend/src/metabase/dashboard/components/ActionViz/ExplainerText/ExplainerText.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { ExternalLink } from "metabase/common/components/ExternalLink"; diff --git a/frontend/src/metabase/dashboard/components/DashCard/DashCard.tsx b/frontend/src/metabase/dashboard/components/DashCard/DashCard.tsx index c0aa73af656..1e028e89820 100644 --- a/frontend/src/metabase/dashboard/components/DashCard/DashCard.tsx +++ b/frontend/src/metabase/dashboard/components/DashCard/DashCard.tsx @@ -14,8 +14,8 @@ import { getDashcardResultsError, isDashcardLoading, } from "metabase/dashboard/utils"; -import EmbedFrameS from "metabase/embedding/theme.module.css"; import { isEmbeddingSdk } from "metabase/embedding-sdk/config"; +import EmbedFrameS from "metabase/embedding/theme.module.css"; import { useQuestionFromCard } from "metabase/metadata-store"; import type { NewParameterOpts } from "metabase/parameters/utils/dashboards"; import { PLUGIN_COLLECTIONS } from "metabase/plugins"; diff --git a/frontend/src/metabase/dashboard/components/DashCard/DashCardActionsPanel/DashCardActionButton/DashCardActionButton.tsx b/frontend/src/metabase/dashboard/components/DashCard/DashCardActionsPanel/DashCardActionButton/DashCardActionButton.tsx index 0318802527b..65b96cd7f8c 100644 --- a/frontend/src/metabase/dashboard/components/DashCard/DashCardActionsPanel/DashCardActionButton/DashCardActionButton.tsx +++ b/frontend/src/metabase/dashboard/components/DashCard/DashCardActionsPanel/DashCardActionButton/DashCardActionButton.tsx @@ -2,8 +2,7 @@ import cx from "classnames"; import type { ElementType, HTMLAttributes } from "react"; import { forwardRef } from "react"; -import { Icon, type IconProps } from "metabase/ui"; -import { Tooltip } from "metabase/ui"; +import { Icon, type IconProps, Tooltip } from "metabase/ui"; import S from "./DashCardActionButton.module.css"; diff --git a/frontend/src/metabase/dashboard/components/Dashboard/Dashboard.tsx b/frontend/src/metabase/dashboard/components/Dashboard/Dashboard.tsx index 64d05d61c8f..d6f352a279d 100644 --- a/frontend/src/metabase/dashboard/components/Dashboard/Dashboard.tsx +++ b/frontend/src/metabase/dashboard/components/Dashboard/Dashboard.tsx @@ -6,8 +6,8 @@ import DashboardS from "metabase/css/dashboard.module.css"; import { DashboardHeader } from "metabase/dashboard/components/DashboardHeader"; import { useDashboardContext } from "metabase/dashboard/context"; import { getIsHeaderVisible } from "metabase/dashboard/selectors"; -import EmbedFrameS from "metabase/embedding/theme.module.css"; import { isEmbeddingSdk } from "metabase/embedding-sdk/config"; +import EmbedFrameS from "metabase/embedding/theme.module.css"; import { useSelector } from "metabase/redux"; import { FullWidthContainer } from "metabase/styled-components/layout/FullWidthContainer"; import { Box, Flex, Loader } from "metabase/ui"; diff --git a/frontend/src/metabase/dashboard/components/grid/GridLayout.tsx b/frontend/src/metabase/dashboard/components/grid/GridLayout.tsx index 17f16fdc3d3..a7ebc07f24f 100644 --- a/frontend/src/metabase/dashboard/components/grid/GridLayout.tsx +++ b/frontend/src/metabase/dashboard/components/grid/GridLayout.tsx @@ -5,10 +5,8 @@ import { } from "react-grid-layout"; import { useMantineTheme } from "metabase/ui"; - import "react-grid-layout/css/styles.css"; import "react-resizable/css/styles.css"; - import { generateGridBackground } from "./utils"; // We need to omit onLayoutChange and margin from the props of ReactGridLayout diff --git a/frontend/src/metabase/dashboard/selectors.ts b/frontend/src/metabase/dashboard/selectors.ts index 67be27ad9e6..d84238ee64c 100644 --- a/frontend/src/metabase/dashboard/selectors.ts +++ b/frontend/src/metabase/dashboard/selectors.ts @@ -6,10 +6,10 @@ import { DASHBOARD_SLOW_TIMEOUT, SIDEBAR_NAME, } from "metabase/dashboard/constants"; -import { getEmbedOptions } from "metabase/embedding/interactive-embedding"; -import { getIsWebApp } from "metabase/embedding/selectors"; import { isEmbeddingSdk } from "metabase/embedding-sdk/config"; import type { SdkSharedStoreState } from "metabase/embedding-sdk/types/store"; +import { getEmbedOptions } from "metabase/embedding/interactive-embedding"; +import { getIsWebApp } from "metabase/embedding/selectors"; import { getShallowDatabases, selectQuestionFromCardBuilder, diff --git a/frontend/src/metabase/dashboard/visualizations/Heading/Heading.styled.tsx b/frontend/src/metabase/dashboard/visualizations/Heading/Heading.styled.tsx index 27861d82ac3..617999670b3 100644 --- a/frontend/src/metabase/dashboard/visualizations/Heading/Heading.styled.tsx +++ b/frontend/src/metabase/dashboard/visualizations/Heading/Heading.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import DashboardS from "metabase/css/dashboard.module.css"; diff --git a/frontend/src/metabase/dashboard/visualizations/IFrameViz/IFrameViz.styled.tsx b/frontend/src/metabase/dashboard/visualizations/IFrameViz/IFrameViz.styled.tsx index 87c6bdb1251..68b4fbb866c 100644 --- a/frontend/src/metabase/dashboard/visualizations/IFrameViz/IFrameViz.styled.tsx +++ b/frontend/src/metabase/dashboard/visualizations/IFrameViz/IFrameViz.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Text, type TextProps, Textarea } from "metabase/ui"; diff --git a/frontend/src/metabase/dashboard/visualizations/LinkViz/EntityDisplay.styled.tsx b/frontend/src/metabase/dashboard/visualizations/LinkViz/EntityDisplay.styled.tsx index 7d53f8d1421..c82f3070148 100644 --- a/frontend/src/metabase/dashboard/visualizations/LinkViz/EntityDisplay.styled.tsx +++ b/frontend/src/metabase/dashboard/visualizations/LinkViz/EntityDisplay.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Ellipsified } from "metabase/ui"; diff --git a/frontend/src/metabase/dashboard/visualizations/LinkViz/LinkViz.styled.tsx b/frontend/src/metabase/dashboard/visualizations/LinkViz/LinkViz.styled.tsx index afe0d46d4ab..cfa10189781 100644 --- a/frontend/src/metabase/dashboard/visualizations/LinkViz/LinkViz.styled.tsx +++ b/frontend/src/metabase/dashboard/visualizations/LinkViz/LinkViz.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { ExternalLink as BaseExternalLink } from "metabase/common/components/ExternalLink"; diff --git a/frontend/src/metabase/dashboard/visualizations/Text/Text.styled.tsx b/frontend/src/metabase/dashboard/visualizations/Text/Text.styled.tsx index a1fef4b7a23..46e3968c4a6 100644 --- a/frontend/src/metabase/dashboard/visualizations/Text/Text.styled.tsx +++ b/frontend/src/metabase/dashboard/visualizations/Text/Text.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import DashboardS from "metabase/css/dashboard.module.css"; diff --git a/frontend/src/metabase/data-grid/types.ts b/frontend/src/metabase/data-grid/types.ts index b60f2ed0644..99baf41d8ba 100644 --- a/frontend/src/metabase/data-grid/types.ts +++ b/frontend/src/metabase/data-grid/types.ts @@ -21,7 +21,6 @@ import type { ColumnsReordering } from "./hooks/use-columns-reordering"; import type { VirtualGrid } from "./hooks/use-virtual-grid"; declare module "@tanstack/react-table" { - // eslint-disable-next-line @typescript-eslint/no-unused-vars interface ColumnMeta { wrap?: boolean; enableReordering?: boolean; diff --git a/frontend/src/metabase/documents/components/DocumentHeader.tsx b/frontend/src/metabase/documents/components/DocumentHeader.tsx index 305f5fc1009..40eb0b7473c 100644 --- a/frontend/src/metabase/documents/components/DocumentHeader.tsx +++ b/frontend/src/metabase/documents/components/DocumentHeader.tsx @@ -34,8 +34,8 @@ import type { Document } from "metabase-types/api"; import { trackDocumentPrint } from "../analytics"; import { DOCUMENT_TITLE_MAX_LENGTH } from "../constants"; -import { DocumentPublicLinkPopover } from "./DocumentHeader/DocumentPublicLinkPopover/DocumentPublicLinkPopover"; import S from "./DocumentHeader.module.css"; +import { DocumentPublicLinkPopover } from "./DocumentHeader/DocumentPublicLinkPopover/DocumentPublicLinkPopover"; const saveButtonTransition: TransitionProps["transition"] = { in: { opacity: 1, visibility: "visible", width: "auto" }, diff --git a/frontend/src/metabase/documents/components/editor-extensions/CardEmbed/CardEmbedNode.tsx b/frontend/src/metabase/documents/components/editor-extensions/CardEmbed/CardEmbedNode.tsx index 86df4920db4..5b7e87cd5f2 100644 --- a/frontend/src/metabase/documents/components/editor-extensions/CardEmbed/CardEmbedNode.tsx +++ b/frontend/src/metabase/documents/components/editor-extensions/CardEmbed/CardEmbedNode.tsx @@ -229,6 +229,7 @@ export const CardEmbed: Node<{ }); export const CardEmbedComponent = memo( + /* eslint-disable complexity -- ESLint and oxlint report different positions within this function header. */ ({ node, updateAttributes, @@ -236,8 +237,8 @@ export const CardEmbedComponent = memo( editor, getPos, deleteNode, - // eslint-disable-next-line complexity }: NodeViewProps) => { + /* eslint-enable complexity */ const { _id, id, name } = node.attrs; const storedResultId = node.attrs.stored_result_id; const isStatic = storedResultId != null; diff --git a/frontend/src/metabase/embedding-sdk/lib/define-global-dependencies.ts b/frontend/src/metabase/embedding-sdk/lib/define-global-dependencies.ts index e66a1500e0f..8b0395bf262 100644 --- a/frontend/src/metabase/embedding-sdk/lib/define-global-dependencies.ts +++ b/frontend/src/metabase/embedding-sdk/lib/define-global-dependencies.ts @@ -1,8 +1,8 @@ import * as React from "react"; -import * as ReactJSXRuntime from "react/jsx-runtime"; import * as ReactDOM from "react-dom"; import * as ReactDOMClient from "react-dom/client"; import * as ReactDOMServer from "react-dom/server"; +import * as ReactJSXRuntime from "react/jsx-runtime"; import type { EXTERNAL_DEPENDENCIES } from "../../../../build/embedding-sdk/constants/external-dependencies"; diff --git a/frontend/src/metabase/embedding-sdk/theme/dynamic-css-vars.ts b/frontend/src/metabase/embedding-sdk/theme/dynamic-css-vars.ts index b6e7049af01..bd788a88e24 100644 --- a/frontend/src/metabase/embedding-sdk/theme/dynamic-css-vars.ts +++ b/frontend/src/metabase/embedding-sdk/theme/dynamic-css-vars.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; import Color from "color"; diff --git a/frontend/src/metabase/embedding-sdk/types/plugins.ts b/frontend/src/metabase/embedding-sdk/types/plugins.ts index da398266dc6..ad03fb1715c 100644 --- a/frontend/src/metabase/embedding-sdk/types/plugins.ts +++ b/frontend/src/metabase/embedding-sdk/types/plugins.ts @@ -1,4 +1,4 @@ -/* eslint-disable-next-line no-restricted-imports */ +/* eslint-disable-next-line eslint-js/no-restricted-imports */ import type { MantineColor } from "@mantine/core"; import type { ReactNode } from "react"; diff --git a/frontend/src/metabase/embedding-sdk/vendors-side-effects.ts b/frontend/src/metabase/embedding-sdk/vendors-side-effects.ts index 3b6d902eb2d..fce4c137d06 100644 --- a/frontend/src/metabase/embedding-sdk/vendors-side-effects.ts +++ b/frontend/src/metabase/embedding-sdk/vendors-side-effects.ts @@ -1,8 +1,6 @@ import "@mantine/core/styles.css"; import "@mantine/dates/styles.css"; - import "metabase/css/index.module.css"; - // The SDK bundle reads the CSP nonce through get-nonce, so it is set here. // Otherwise nothing in the bundle sets it. import "metabase/utils/csp-setup"; diff --git a/frontend/src/metabase/embedding/components/EmbedFooter.styled.tsx b/frontend/src/metabase/embedding/components/EmbedFooter.styled.tsx index 7b711375211..917ee8e8aed 100644 --- a/frontend/src/metabase/embedding/components/EmbedFooter.styled.tsx +++ b/frontend/src/metabase/embedding/components/EmbedFooter.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { diff --git a/frontend/src/metabase/embedding/components/EmbedModal/StaticEmbedSetupPane/CodeSample.styled.tsx b/frontend/src/metabase/embedding/components/EmbedModal/StaticEmbedSetupPane/CodeSample.styled.tsx index 7395ed69971..c1ca8ebd06f 100644 --- a/frontend/src/metabase/embedding/components/EmbedModal/StaticEmbedSetupPane/CodeSample.styled.tsx +++ b/frontend/src/metabase/embedding/components/EmbedModal/StaticEmbedSetupPane/CodeSample.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const CopyButtonContainer = styled.div` diff --git a/frontend/src/metabase/embedding/components/EmbedModal/StaticEmbedSetupPane/StaticEmbedSetupPane.styled.tsx b/frontend/src/metabase/embedding/components/EmbedModal/StaticEmbedSetupPane/StaticEmbedSetupPane.styled.tsx index ccbec3a936f..31090d91709 100644 --- a/frontend/src/metabase/embedding/components/EmbedModal/StaticEmbedSetupPane/StaticEmbedSetupPane.styled.tsx +++ b/frontend/src/metabase/embedding/components/EmbedModal/StaticEmbedSetupPane/StaticEmbedSetupPane.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import type { ReactNode } from "react"; diff --git a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/analytics.ts b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/analytics.ts index e732a30ff0a..297f4ccd3ac 100644 --- a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/analytics.ts +++ b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/analytics.ts @@ -1,12 +1,12 @@ import _ from "underscore"; import { trackSimpleEvent } from "metabase/analytics"; +import { getAuthSubTypeForSettings } from "metabase/embedding/embedding-iframe-sdk-setup/utils/get-auth-sub-type-for-settings"; +import { getAuthTypeForSettings } from "metabase/embedding/embedding-iframe-sdk-setup/utils/get-auth-type-for-settings"; import type { SdkIframeEmbedSettingKey, SdkIframeEmbedSettings, } from "metabase/embedding/embedding-iframe-sdk/types/embed"; -import { getAuthSubTypeForSettings } from "metabase/embedding/embedding-iframe-sdk-setup/utils/get-auth-sub-type-for-settings"; -import { getAuthTypeForSettings } from "metabase/embedding/embedding-iframe-sdk-setup/utils/get-auth-type-for-settings"; import { countEmbeddingParameterOptions } from "metabase/embedding/lib/count-embedding-parameter-options"; import type { SdkIframeEmbedSetupExperience, diff --git a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/components/SdkIframeEmbedPreview.tsx b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/components/SdkIframeEmbedPreview.tsx index e3a0cead9a7..f8daa8f647a 100644 --- a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/components/SdkIframeEmbedPreview.tsx +++ b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/components/SdkIframeEmbedPreview.tsx @@ -10,13 +10,13 @@ import { match } from "ts-pattern"; import { applyThemePreset } from "embedding-sdk-shared/lib/apply-theme-preset"; import { useListEmbeddingThemesQuery } from "metabase/api/embedding-theme"; +import type { MetabaseTheme } from "metabase/embedding-sdk/theme"; +import { buildEmbedAttributes } from "metabase/embedding/embedding-iframe-sdk-setup/utils/build-embed-attributes"; import { METABASE_CONFIG_IS_PROXY_FIELD_NAME } from "metabase/embedding/embedding-iframe-sdk/constants"; // we import the equivalent of embed.js so that we don't add extra loading time // by appending the script import { setupConfigWatcher } from "metabase/embedding/embedding-iframe-sdk/embed"; import type { SdkIframeEmbedBaseSettings } from "metabase/embedding/embedding-iframe-sdk/types/embed"; -import { buildEmbedAttributes } from "metabase/embedding/embedding-iframe-sdk-setup/utils/build-embed-attributes"; -import type { MetabaseTheme } from "metabase/embedding-sdk/theme"; import { useSetting } from "metabase/settings"; import { Card } from "metabase/ui"; import { colors as defaultMetabaseColors } from "metabase/ui/colors"; diff --git a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/components/SdkIframeEmbedSetupModal.tsx b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/components/SdkIframeEmbedSetupModal.tsx index 524c80450ea..41dc4ad3e94 100644 --- a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/components/SdkIframeEmbedSetupModal.tsx +++ b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/components/SdkIframeEmbedSetupModal.tsx @@ -2,9 +2,7 @@ import { useMemo, useState } from "react"; import { ResizableBox } from "react-resizable"; import { match } from "ts-pattern"; import { t } from "ttag"; - import "react-resizable/css/styles.css"; - import noResultsSource from "assets/img/no_results.svg"; import { SdkIframeGuestEmbedStatusBar } from "metabase/embedding/embedding-iframe-sdk-setup/components/SdkIframeGuestEmbedStatusBar"; import { EMBED_STEPS } from "metabase/embedding/embedding-iframe-sdk-setup/constants"; diff --git a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/hooks/use-handle-experience-change.ts b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/hooks/use-handle-experience-change.ts index 3fcea1ca952..1acab7a779c 100644 --- a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/hooks/use-handle-experience-change.ts +++ b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/hooks/use-handle-experience-change.ts @@ -2,11 +2,11 @@ import { useCallback } from "react"; import { P, match } from "ts-pattern"; import _ from "underscore"; -import { ALLOWED_EMBED_SETTING_KEYS_MAP } from "metabase/embedding/embedding-iframe-sdk/constants"; import { EMBED_FALLBACK_QUESTION_ID } from "metabase/embedding/embedding-iframe-sdk-setup/constants"; import { useSdkIframeEmbedSetupContext } from "metabase/embedding/embedding-iframe-sdk-setup/context"; import { determineDashboardId } from "metabase/embedding/embedding-iframe-sdk-setup/utils/determine-dashboard-id"; import { getDefaultSdkIframeEmbedSettings } from "metabase/embedding/embedding-iframe-sdk-setup/utils/get-default-sdk-iframe-embed-setting"; +import { ALLOWED_EMBED_SETTING_KEYS_MAP } from "metabase/embedding/embedding-iframe-sdk/constants"; import type { SdkIframeEmbedSetupExperience } from "metabase/embedding/types"; import { useSetting } from "metabase/settings"; diff --git a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/hooks/use-initial-parameter-values.ts b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/hooks/use-initial-parameter-values.ts index d1d639d5736..377cb731f36 100644 --- a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/hooks/use-initial-parameter-values.ts +++ b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/hooks/use-initial-parameter-values.ts @@ -1,9 +1,9 @@ import { useDebouncedCallback } from "@mantine/hooks"; import { useCallback, useMemo } from "react"; +import type { ParameterValues } from "metabase/embedding-sdk/types/dashboard"; import { SET_INITIAL_PARAMETER_DEBOUNCE_MS } from "metabase/embedding/embedding-iframe-sdk-setup/constants"; import type { SdkIframeEmbedSetupContextType } from "metabase/embedding/embedding-iframe-sdk-setup/context"; -import type { ParameterValues } from "metabase/embedding-sdk/types/dashboard"; import type { ParameterValueOrArray } from "metabase-types/api"; type ParameterValuesKey = "initialParameters" | "initialSqlParameters"; diff --git a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/build-embed-attributes.ts b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/build-embed-attributes.ts index d7d72e2b993..cb6c055228c 100644 --- a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/build-embed-attributes.ts +++ b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/build-embed-attributes.ts @@ -1,5 +1,7 @@ import { match } from "ts-pattern"; +import type { SdkIframeEmbedSetupSettings } from "metabase/embedding/embedding-iframe-sdk-setup/types"; +import { getVisibleParameters } from "metabase/embedding/embedding-iframe-sdk-setup/utils/get-visible-parameters"; import { ALLOWED_EMBED_SETTING_KEYS_MAP, ALLOWED_GUEST_EMBED_SETTING_KEYS_MAP, @@ -13,8 +15,6 @@ import type { SdkIframeEmbedBaseSettings, SdkIframeQuestionEmbedSettings, } from "metabase/embedding/embedding-iframe-sdk/types/embed"; -import type { SdkIframeEmbedSetupSettings } from "metabase/embedding/embedding-iframe-sdk-setup/types"; -import { getVisibleParameters } from "metabase/embedding/embedding-iframe-sdk-setup/utils/get-visible-parameters"; import type { SdkIframeEmbedSetupExperience } from "metabase/embedding/types"; import type { EntityToken } from "metabase-types/api/entity"; diff --git a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/embed-snippet.ts b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/embed-snippet.ts index ee61d78de35..bba01843177 100644 --- a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/embed-snippet.ts +++ b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/embed-snippet.ts @@ -1,11 +1,11 @@ import { match } from "ts-pattern"; import _ from "underscore"; +import { buildEmbedAttributes } from "metabase/embedding/embedding-iframe-sdk-setup/utils/build-embed-attributes"; import { ALLOWED_EMBED_SETTING_KEYS_MAP, ALLOWED_GUEST_EMBED_SETTING_KEYS_MAP, } from "metabase/embedding/embedding-iframe-sdk/constants"; -import { buildEmbedAttributes } from "metabase/embedding/embedding-iframe-sdk-setup/utils/build-embed-attributes"; import type { SdkIframeEmbedSetupExperience } from "metabase/embedding/types"; import type { SdkIframeEmbedSetupSettings } from "../types"; diff --git a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/get-common-embed-settings.ts b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/get-common-embed-settings.ts index dc8934736ad..8559ecb99f2 100644 --- a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/get-common-embed-settings.ts +++ b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/get-common-embed-settings.ts @@ -1,8 +1,8 @@ +import { hasAuthToSelect } from "metabase/embedding/embedding-iframe-sdk-setup/utils/has-auth-to-select"; import type { SdkIframeDashboardEmbedSettings, SdkIframeQuestionEmbedSettings, } from "metabase/embedding/embedding-iframe-sdk/types/embed"; -import { hasAuthToSelect } from "metabase/embedding/embedding-iframe-sdk-setup/utils/has-auth-to-select"; import type { SdkIframeEmbedSetupExperience } from "metabase/embedding/types"; import { PLUGIN_EMBEDDING_IFRAME_SDK_SETUP } from "metabase/plugins"; diff --git a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/is-question-or-dashboard-settings.ts b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/is-question-or-dashboard-settings.ts index cd45c62099f..212e71aa298 100644 --- a/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/is-question-or-dashboard-settings.ts +++ b/frontend/src/metabase/embedding/embedding-iframe-sdk-setup/utils/is-question-or-dashboard-settings.ts @@ -1,8 +1,8 @@ +import type { SdkIframeEmbedSetupSettings } from "metabase/embedding/embedding-iframe-sdk-setup/types"; import type { SdkIframeDashboardEmbedSettings, SdkIframeQuestionEmbedSettings, } from "metabase/embedding/embedding-iframe-sdk/types/embed"; -import type { SdkIframeEmbedSetupSettings } from "metabase/embedding/embedding-iframe-sdk-setup/types"; import type { SdkIframeEmbedSetupExperience } from "metabase/embedding/types"; export const isQuestionOrDashboardSettings = ( diff --git a/frontend/src/metabase/embedding/embedding-iframe-sdk/components/SdkIframeEmbedRoute.tsx b/frontend/src/metabase/embedding/embedding-iframe-sdk/components/SdkIframeEmbedRoute.tsx index d3d12eb53bf..565b19e2522 100644 --- a/frontend/src/metabase/embedding/embedding-iframe-sdk/components/SdkIframeEmbedRoute.tsx +++ b/frontend/src/metabase/embedding/embedding-iframe-sdk/components/SdkIframeEmbedRoute.tsx @@ -23,8 +23,8 @@ import type { MetabaseAuthConfig } from "embedding-sdk-shared/types/auth-config" import { createSnowplowTracker } from "metabase/analytics"; import { type OnBeforeRequestHandler, PLUGIN_API } from "metabase/api/client"; import { getUserId } from "metabase/current-user"; -import { EmbeddingFooter } from "metabase/embedding/components/EmbeddingFooter/EmbeddingFooter"; import { EMBEDDING_SDK_IFRAME_EMBEDDING_CONFIG } from "metabase/embedding-sdk/config"; +import { EmbeddingFooter } from "metabase/embedding/components/EmbeddingFooter/EmbeddingFooter"; import { PLUGIN_EMBEDDING_IFRAME_SDK } from "metabase/plugins"; import { useSelector } from "metabase/redux"; import { getSetting } from "metabase/settings"; diff --git a/frontend/src/metabase/embedding/embedding-iframe-sdk/embed.ts b/frontend/src/metabase/embedding/embedding-iframe-sdk/embed.ts index 95aff8a8fd7..f85b4a8916f 100644 --- a/frontend/src/metabase/embedding/embedding-iframe-sdk/embed.ts +++ b/frontend/src/metabase/embedding/embedding-iframe-sdk/embed.ts @@ -1,13 +1,13 @@ import type { SqlParameterValues } from "embedding-sdk-bundle/types"; import { MetabaseError, SSO_NOT_ALLOWED } from "embedding-sdk-shared/errors"; import * as MetabaseErrors from "embedding-sdk-shared/errors"; +import type { ParameterValues } from "metabase/embedding-sdk/types/dashboard"; import { PLUGIN_EMBED_JS_EE } from "metabase/embedding/embedding-iframe-sdk/plugin"; import type { EmbedAuthManager, EmbedAuthManagerContext, } from "metabase/embedding/embedding-iframe-sdk/types/auth-manager"; import type { ComponentToAttributes } from "metabase/embedding/embedding-iframe-sdk/types/modular-embedding"; -import type { ParameterValues } from "metabase/embedding-sdk/types/dashboard"; import { decodeJwt } from "metabase/utils/jwt"; import { debouncedReportAnalytics } from "./analytics"; @@ -27,7 +27,6 @@ import type { } from "./types/embed"; import { listenForEajsMessages } from "./utils/post-message"; import { attributeToSettingKey, parseAttributeValue } from "./webcomponents"; - // Import EE Iframe Embedding script plugins import "sdk-iframe-embedding-script-ee-plugins"; diff --git a/frontend/src/metabase/embedding/embedding-iframe-sdk/types/auth-manager.ts b/frontend/src/metabase/embedding/embedding-iframe-sdk/types/auth-manager.ts index c7ae15bd56d..3fc62cbc1b9 100644 --- a/frontend/src/metabase/embedding/embedding-iframe-sdk/types/auth-manager.ts +++ b/frontend/src/metabase/embedding/embedding-iframe-sdk/types/auth-manager.ts @@ -1,6 +1,6 @@ import type { MetabaseAuthMethod } from "embedding-sdk-bundle/types"; -import type { SdkIframeEmbedMessage } from "metabase/embedding/embedding-iframe-sdk/types/embed"; import type { MetabaseFetchRequestTokenFn } from "metabase/embedding-sdk/types/refresh-token"; +import type { SdkIframeEmbedMessage } from "metabase/embedding/embedding-iframe-sdk/types/embed"; export interface EmbedAuthManagerContext { properties: { diff --git a/frontend/src/metabase/embedding/lib/auth/set-embed-preview-header.ts b/frontend/src/metabase/embedding/lib/auth/set-embed-preview-header.ts index 0903b7fdb03..08608144150 100644 --- a/frontend/src/metabase/embedding/lib/auth/set-embed-preview-header.ts +++ b/frontend/src/metabase/embedding/lib/auth/set-embed-preview-header.ts @@ -1,7 +1,7 @@ /* eslint-disable metabase/no-literal-metabase-strings -- request header names */ import type { OnBeforeRequestHandler } from "metabase/api/client"; -import { isEmbedPreview } from "metabase/embedding/config"; import { isDataAppDev } from "metabase/embedding-sdk/config"; +import { isEmbedPreview } from "metabase/embedding/config"; export const isEmbedPreviewRequest = () => isEmbedPreview() || isDataAppDev(); diff --git a/frontend/src/metabase/home/components/EmbedHomepage/EmbedJsContent.tsx b/frontend/src/metabase/home/components/EmbedHomepage/EmbedJsContent.tsx index 9aea38bae30..78aee8ef098 100644 --- a/frontend/src/metabase/home/components/EmbedHomepage/EmbedJsContent.tsx +++ b/frontend/src/metabase/home/components/EmbedHomepage/EmbedJsContent.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { match } from "ts-pattern"; import { t } from "ttag"; diff --git a/frontend/src/metabase/home/components/EmbedHomepage/StaticEmbedContent.tsx b/frontend/src/metabase/home/components/EmbedHomepage/StaticEmbedContent.tsx index 5d3bfdbee85..f8e45dd201d 100644 --- a/frontend/src/metabase/home/components/EmbedHomepage/StaticEmbedContent.tsx +++ b/frontend/src/metabase/home/components/EmbedHomepage/StaticEmbedContent.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { t } from "ttag"; diff --git a/frontend/src/metabase/metadata/components/UserInput.tsx b/frontend/src/metabase/metadata/components/UserInput.tsx index 04e50f761d6..bea125a037d 100644 --- a/frontend/src/metabase/metadata/components/UserInput.tsx +++ b/frontend/src/metabase/metadata/components/UserInput.tsx @@ -2,8 +2,14 @@ import { useMemo, useState } from "react"; import { t } from "ttag"; import { useListUsersQuery } from "metabase/api"; -import { Ellipsified } from "metabase/ui"; -import { Avatar, Flex, Icon, Select, type SelectProps } from "metabase/ui"; +import { + Avatar, + Ellipsified, + Flex, + Icon, + Select, + type SelectProps, +} from "metabase/ui"; import { isEmail } from "metabase/utils/email"; import type { User, UserId } from "metabase-types/api"; interface Props extends Omit { diff --git a/frontend/src/metabase/metrics-viewer/components/FilterPopover/utils.unit.spec.ts b/frontend/src/metabase/metrics-viewer/components/FilterPopover/utils.unit.spec.ts index 2e1710ae89b..a69fe2a9188 100644 --- a/frontend/src/metabase/metrics-viewer/components/FilterPopover/utils.unit.spec.ts +++ b/frontend/src/metabase/metrics-viewer/components/FilterPopover/utils.unit.spec.ts @@ -1,10 +1,9 @@ import type { DefinitionSource } from "metabase/metrics-viewer/utils/definition-sources"; -import type * as LibMetric from "metabase-lib/metric"; import type { DimensionDisplayInfo, SegmentDisplayInfo, } from "metabase-lib/metric"; -import * as LibMetricModule from "metabase-lib/metric"; +import * as LibMetric from "metabase-lib/metric"; import { filterDisplayGroupsBySearch, getMetricGroups } from "./utils"; @@ -22,7 +21,7 @@ jest.mock("metabase/metrics-viewer/utils/definition-sources", () => ({ })); // Unjustified type cast. FIXME -const mockLibMetric = LibMetricModule as jest.Mocked; +const mockLibMetric = LibMetric as jest.Mocked; type TestItem = { name: string }; diff --git a/frontend/src/metabase/metrics-viewer/components/MetricSearch/utils.unit.spec.ts b/frontend/src/metabase/metrics-viewer/components/MetricSearch/utils.unit.spec.ts index ecded3122f2..731e01dab95 100644 --- a/frontend/src/metabase/metrics-viewer/components/MetricSearch/utils.unit.spec.ts +++ b/frontend/src/metabase/metrics-viewer/components/MetricSearch/utils.unit.spec.ts @@ -378,7 +378,7 @@ describe("parseFullText — numeric literal parsing", () => { expect(result).toHaveLength(1); const entry = result[0]; if (entry.type === "expression") { - // eslint-disable-next-line jest/no-conditional-expect + // eslint-disable-next-line jest-js/no-conditional-expect expect(buildExpressionText(entry.tokens, metricNames)).toBe(text); } else { throw new Error("Expected expression entry"); @@ -490,7 +490,7 @@ describe("parseFullText — negative numbers", () => { expect(result).toHaveLength(1); const entry = result[0]; if (entry.type === "expression") { - // eslint-disable-next-line jest/no-conditional-expect + // eslint-disable-next-line jest-js/no-conditional-expect expect(buildExpressionText(entry.tokens, metricNames)).toBe(text); } else { throw new Error("Expected expression entry"); diff --git a/frontend/src/metabase/metrics-viewer/components/MetricsFilterPills/MetricsFilterPills.unit.spec.tsx b/frontend/src/metabase/metrics-viewer/components/MetricsFilterPills/MetricsFilterPills.unit.spec.tsx index f4ad2803679..c65f142a71e 100644 --- a/frontend/src/metabase/metrics-viewer/components/MetricsFilterPills/MetricsFilterPills.unit.spec.tsx +++ b/frontend/src/metabase/metrics-viewer/components/MetricsFilterPills/MetricsFilterPills.unit.spec.tsx @@ -2,8 +2,7 @@ import userEvent from "@testing-library/user-event"; import { renderWithProviders, screen } from "__support__/ui"; import type { DefinitionSource } from "metabase/metrics-viewer/utils/definition-sources"; -import type * as LibMetric from "metabase-lib/metric"; -import * as LibMetricModule from "metabase-lib/metric"; +import * as LibMetric from "metabase-lib/metric"; import { MetricsFilterPills } from "./MetricsFilterPills"; @@ -72,7 +71,7 @@ jest.mock("./MetricsSegmentFilterPillPopover", () => ({ })); // Unjustified type cast. FIXME -const mockLibMetric = LibMetricModule as jest.Mocked; +const mockLibMetric = LibMetric as jest.Mocked; function makeDefinitionSource( index: number, @@ -83,7 +82,9 @@ function makeDefinitionSource( // Unjustified type cast. FIXME id: `src-${index}` as DefinitionSource["id"], // Unjustified type cast. FIXME - definition: { __fakeDef: index } as unknown as LibMetric.MetricDefinition, + definition: { + __fakeDef: index, + } as unknown as LibMetric.MetricDefinition, // Unjustified type cast. FIXME entity: { type: "metric", diff --git a/frontend/src/metabase/metrics-viewer/components/MetricsFilterPills/MetricsSegmentFilterPillPopover.unit.spec.tsx b/frontend/src/metabase/metrics-viewer/components/MetricsFilterPills/MetricsSegmentFilterPillPopover.unit.spec.tsx index db485959c0e..aead3d54420 100644 --- a/frontend/src/metabase/metrics-viewer/components/MetricsFilterPills/MetricsSegmentFilterPillPopover.unit.spec.tsx +++ b/frontend/src/metabase/metrics-viewer/components/MetricsFilterPills/MetricsSegmentFilterPillPopover.unit.spec.tsx @@ -6,8 +6,7 @@ import { trackMetricsViewerFilterRemoved, } from "metabase/metrics-viewer/analytics"; import type { DefinitionSource } from "metabase/metrics-viewer/utils/definition-sources"; -import type * as LibMetric from "metabase-lib/metric"; -import * as LibMetricModule from "metabase-lib/metric"; +import * as LibMetric from "metabase-lib/metric"; import { MetricsSegmentFilterPillPopover } from "./MetricsSegmentFilterPillPopover"; @@ -50,7 +49,7 @@ jest.mock("../FilterPopover/FilterPopoverContent", () => ({ })); // Unjustified type cast. FIXME -const mockLibMetric = LibMetricModule as jest.Mocked; +const mockLibMetric = LibMetric as jest.Mocked; // Unjustified type cast. FIXME const mockTrackEdited = trackMetricsViewerFilterEdited as jest.MockedFunction< typeof trackMetricsViewerFilterEdited diff --git a/frontend/src/metabase/metrics-viewer/types/viewer-state.ts b/frontend/src/metabase/metrics-viewer/types/viewer-state.ts index 6f55d1586d5..b2147b6a9b8 100644 --- a/frontend/src/metabase/metrics-viewer/types/viewer-state.ts +++ b/frontend/src/metabase/metrics-viewer/types/viewer-state.ts @@ -1,9 +1,9 @@ -import type { GeoSubtype } from "metabase/common/metrics/utils/dimension-types"; import type { MetricsViewerDimensionBreakoutType, MetricsViewerDisplayType, SerializedDefinitionInfo, } from "metabase/common/metrics-viewer"; +import type { GeoSubtype } from "metabase/common/metrics/utils/dimension-types"; import type { DimensionGroup, MetricDefinition, diff --git a/frontend/src/metabase/metrics-viewer/utils/legend.ts b/frontend/src/metabase/metrics-viewer/utils/legend.ts index 43fcb85b027..4889d21d12b 100644 --- a/frontend/src/metabase/metrics-viewer/utils/legend.ts +++ b/frontend/src/metabase/metrics-viewer/utils/legend.ts @@ -6,8 +6,8 @@ import { type MetricsViewerFormulaEntity, type SourceBreakoutColorMap, isExpressionEntry, + isMetricEntry, } from "../types/viewer-state"; -import { isMetricEntry } from "../types/viewer-state"; import { getDefinitionName } from "./definition-builder"; import { diff --git a/frontend/src/metabase/metrics/components/MetricDimensionGrid/MetricDimensionGrid.tsx b/frontend/src/metabase/metrics/components/MetricDimensionGrid/MetricDimensionGrid.tsx index cd5dea1ff9f..410cda1fd55 100644 --- a/frontend/src/metabase/metrics/components/MetricDimensionGrid/MetricDimensionGrid.tsx +++ b/frontend/src/metabase/metrics/components/MetricDimensionGrid/MetricDimensionGrid.tsx @@ -3,11 +3,11 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { t } from "ttag"; import _ from "underscore"; +import { exploreMetricDimensionUrl } from "metabase/common/metrics-viewer"; import { DEFAULT_DISPLAY_TYPE_BY_DIMENSION, type DefaultDimensionDisplayType, } from "metabase/common/metrics/utils/dimension-types"; -import { exploreMetricDimensionUrl } from "metabase/common/metrics-viewer"; import { trackMetricPageShowMoreClicked } from "metabase/metrics/analytics"; import { useMetricDimensionQuery } from "metabase/metrics/common/hooks"; import { useNavigate } from "metabase/router"; diff --git a/frontend/src/metabase/models/components/ModelActions/EmptyState.styled.tsx b/frontend/src/metabase/models/components/ModelActions/EmptyState.styled.tsx index 27380c203ae..d64e4422ec6 100644 --- a/frontend/src/metabase/models/components/ModelActions/EmptyState.styled.tsx +++ b/frontend/src/metabase/models/components/ModelActions/EmptyState.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const EmptyStateContainer = styled.div` diff --git a/frontend/src/metabase/models/components/ModelActions/ModelActionDetails/ModelActionDetails.styled.tsx b/frontend/src/metabase/models/components/ModelActions/ModelActionDetails/ModelActionDetails.styled.tsx index 59ffb6879e1..7dbfe8652b9 100644 --- a/frontend/src/metabase/models/components/ModelActions/ModelActionDetails/ModelActionDetails.styled.tsx +++ b/frontend/src/metabase/models/components/ModelActions/ModelActionDetails/ModelActionDetails.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { breakpointMaxMedium } from "metabase/styled-components/theme"; diff --git a/frontend/src/metabase/models/components/ModelActions/ModelActionDetails/ModelActionListItem.styled.tsx b/frontend/src/metabase/models/components/ModelActions/ModelActionDetails/ModelActionListItem.styled.tsx index 82bcd55ee05..fc0bb2a3bb0 100644 --- a/frontend/src/metabase/models/components/ModelActions/ModelActionDetails/ModelActionListItem.styled.tsx +++ b/frontend/src/metabase/models/components/ModelActions/ModelActionDetails/ModelActionListItem.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Link } from "metabase/common/components/Link"; diff --git a/frontend/src/metabase/nav/components/AppBar/AppBarLarge.styled.tsx b/frontend/src/metabase/nav/components/AppBar/AppBarLarge.styled.tsx index 1b631c80b99..e75ec727c8d 100644 --- a/frontend/src/metabase/nav/components/AppBar/AppBarLarge.styled.tsx +++ b/frontend/src/metabase/nav/components/AppBar/AppBarLarge.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { APP_BAR_HEIGHT } from "metabase/nav/constants"; diff --git a/frontend/src/metabase/nav/components/AppBar/AppBarLogo.styled.tsx b/frontend/src/metabase/nav/components/AppBar/AppBarLogo.styled.tsx index 7631be283be..22f2d6d3d32 100644 --- a/frontend/src/metabase/nav/components/AppBar/AppBarLogo.styled.tsx +++ b/frontend/src/metabase/nav/components/AppBar/AppBarLogo.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Link } from "metabase/common/components/Link"; diff --git a/frontend/src/metabase/nav/components/AppBar/AppBarSmall.styled.tsx b/frontend/src/metabase/nav/components/AppBar/AppBarSmall.styled.tsx index fbb9767c5cd..a7c5d83c4bd 100644 --- a/frontend/src/metabase/nav/components/AppBar/AppBarSmall.styled.tsx +++ b/frontend/src/metabase/nav/components/AppBar/AppBarSmall.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { APP_BAR_HEIGHT, APP_SUBHEADER_HEIGHT } from "metabase/nav/constants"; diff --git a/frontend/src/metabase/nav/components/AppBar/AppBarToggle.styled.tsx b/frontend/src/metabase/nav/components/AppBar/AppBarToggle.styled.tsx index d457e7f812c..e072d7f3dcd 100644 --- a/frontend/src/metabase/nav/components/AppBar/AppBarToggle.styled.tsx +++ b/frontend/src/metabase/nav/components/AppBar/AppBarToggle.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Icon } from "metabase/ui"; diff --git a/frontend/src/metabase/nav/containers/MainNavbar/MainNavbar.styled.tsx b/frontend/src/metabase/nav/containers/MainNavbar/MainNavbar.styled.tsx index 1574014405d..f01a20120f9 100644 --- a/frontend/src/metabase/nav/containers/MainNavbar/MainNavbar.styled.tsx +++ b/frontend/src/metabase/nav/containers/MainNavbar/MainNavbar.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { NAV_SIDEBAR_WIDTH } from "metabase/nav/constants"; diff --git a/frontend/src/metabase/nav/containers/MainNavbar/MainNavbarContainer/BookmarkList/BookmarkList.styled.tsx b/frontend/src/metabase/nav/containers/MainNavbar/MainNavbarContainer/BookmarkList/BookmarkList.styled.tsx index d5a85c1b124..c81d76dacb4 100644 --- a/frontend/src/metabase/nav/containers/MainNavbar/MainNavbarContainer/BookmarkList/BookmarkList.styled.tsx +++ b/frontend/src/metabase/nav/containers/MainNavbar/MainNavbarContainer/BookmarkList/BookmarkList.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { color } from "metabase/ui/utils/colors"; diff --git a/frontend/src/metabase/nav/containers/MainNavbar/SidebarItems/DraggableSidebarLink.styled.tsx b/frontend/src/metabase/nav/containers/MainNavbar/SidebarItems/DraggableSidebarLink.styled.tsx index 637867a84a5..9ef3ac8f7bd 100644 --- a/frontend/src/metabase/nav/containers/MainNavbar/SidebarItems/DraggableSidebarLink.styled.tsx +++ b/frontend/src/metabase/nav/containers/MainNavbar/SidebarItems/DraggableSidebarLink.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Icon } from "metabase/ui"; diff --git a/frontend/src/metabase/nav/containers/MainNavbar/SidebarItems/SidebarItems.styled.tsx b/frontend/src/metabase/nav/containers/MainNavbar/SidebarItems/SidebarItems.styled.tsx index 003a2d276dc..630becc1f9a 100644 --- a/frontend/src/metabase/nav/containers/MainNavbar/SidebarItems/SidebarItems.styled.tsx +++ b/frontend/src/metabase/nav/containers/MainNavbar/SidebarItems/SidebarItems.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import type { ComponentProps } from "react"; import { forwardRef } from "react"; diff --git a/frontend/src/metabase/notifications/utils.ts b/frontend/src/metabase/notifications/utils.ts index 03483048987..52d31597cc1 100644 --- a/frontend/src/metabase/notifications/utils.ts +++ b/frontend/src/metabase/notifications/utils.ts @@ -491,7 +491,6 @@ export const ALERT_TYPE_ROWS = "alert-type-rows"; export const ALERT_TYPE_TIMESERIES_GOAL = "alert-type-timeseries-goal"; export const ALERT_TYPE_PROGRESS_BAR_GOAL = "alert-type-progress-bar-goal"; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -- used for types const AlertTypes = [ ALERT_TYPE_ROWS, ALERT_TYPE_TIMESERIES_GOAL, diff --git a/frontend/src/metabase/public/components/EmbedFrame/EmbedFrame.styled.tsx b/frontend/src/metabase/public/components/EmbedFrame/EmbedFrame.styled.tsx index bdf53590115..6f58dd3b624 100644 --- a/frontend/src/metabase/public/components/EmbedFrame/EmbedFrame.styled.tsx +++ b/frontend/src/metabase/public/components/EmbedFrame/EmbedFrame.styled.tsx @@ -1,7 +1,7 @@ import isPropValid from "@emotion/is-prop-valid"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { FixedWidthContainer } from "metabase/dashboard/components/Dashboard/DashboardComponents"; diff --git a/frontend/src/metabase/public/components/EmbedFrame/EmbedFrame.tsx b/frontend/src/metabase/public/components/EmbedFrame/EmbedFrame.tsx index b9fe7d2a2db..5c1ec45c6e4 100644 --- a/frontend/src/metabase/public/components/EmbedFrame/EmbedFrame.tsx +++ b/frontend/src/metabase/public/components/EmbedFrame/EmbedFrame.tsx @@ -11,13 +11,13 @@ import { FixedWidthContainer } from "metabase/dashboard/components/Dashboard/Das import { ExportAsPdfButton } from "metabase/dashboard/components/DashboardHeader/buttons/ExportAsPdfButton"; import { FilterApplyToast } from "metabase/dashboard/components/FilterApplyToast"; import { useIsParameterPanelSticky } from "metabase/dashboard/hooks/use-is-parameter-panel-sticky"; +import { isEmbeddingSdk } from "metabase/embedding-sdk/config"; import { ActionButtonsContainer, type FooterVariant, } from "metabase/embedding/components/EmbedFooter.styled"; import { EmbeddingFooter } from "metabase/embedding/components/EmbeddingFooter/EmbeddingFooter"; import EmbedThemeS from "metabase/embedding/theme.module.css"; -import { isEmbeddingSdk } from "metabase/embedding-sdk/config"; import { ParametersList } from "metabase/parameters/components/ParametersList"; import { SyncedParametersList } from "metabase/parameters/components/SyncedParametersList"; import { useSyncUrlParameters } from "metabase/parameters/components/use-sync-url-parameters"; diff --git a/frontend/src/metabase/public/components/EmbedFrame/useGlobalTheme.tsx b/frontend/src/metabase/public/components/EmbedFrame/useGlobalTheme.tsx index c7af7e173fb..68c36f6637f 100644 --- a/frontend/src/metabase/public/components/EmbedFrame/useGlobalTheme.tsx +++ b/frontend/src/metabase/public/components/EmbedFrame/useGlobalTheme.tsx @@ -1,7 +1,7 @@ import { useEffect } from "react"; -import type { DisplayTheme } from "metabase/embedding/types"; import { isEmbeddingSdk } from "metabase/embedding-sdk/config"; +import type { DisplayTheme } from "metabase/embedding/types"; export function useGlobalTheme(theme: DisplayTheme | undefined) { useEffect(() => { diff --git a/frontend/src/metabase/querying/common/components/DatePicker/DatePicker.stories.tsx b/frontend/src/metabase/querying/common/components/DatePicker/DatePicker.stories.tsx index 768baf00b1a..14f2dc0a736 100644 --- a/frontend/src/metabase/querying/common/components/DatePicker/DatePicker.stories.tsx +++ b/frontend/src/metabase/querying/common/components/DatePicker/DatePicker.stories.tsx @@ -13,7 +13,6 @@ import type { State } from "metabase/redux/store"; import { Box, Popover } from "metabase/ui"; import { DatePicker } from "./DatePicker"; - import "metabase/embedding/theme.module.css"; const storeInitialState = createMockState({ diff --git a/frontend/src/metabase/querying/components/expressions/Editor/Editor.tsx b/frontend/src/metabase/querying/components/expressions/Editor/Editor.tsx index be4e86bc190..4f3e4f01494 100644 --- a/frontend/src/metabase/querying/components/expressions/Editor/Editor.tsx +++ b/frontend/src/metabase/querying/components/expressions/Editor/Editor.tsx @@ -17,8 +17,8 @@ import { diagnoseAndCompile, format, getClauseDefinition, + tokenAtPos, } from "metabase/querying/expressions"; -import { tokenAtPos } from "metabase/querying/expressions"; import { COMMA, GROUP } from "metabase/querying/expressions/pratt"; import { Button, Tooltip as ButtonTooltip, Flex, Icon } from "metabase/ui"; import * as Lib from "metabase-lib"; diff --git a/frontend/src/metabase/querying/components/expressions/Editor/HelpText/HelpText.tsx b/frontend/src/metabase/querying/components/expressions/Editor/HelpText/HelpText.tsx index 25d064de5af..e117ecc4bfa 100644 --- a/frontend/src/metabase/querying/components/expressions/Editor/HelpText/HelpText.tsx +++ b/frontend/src/metabase/querying/components/expressions/Editor/HelpText/HelpText.tsx @@ -13,7 +13,6 @@ import { Markdown } from "metabase/common/components/Markdown"; import { useDocsUrl } from "metabase/common/hooks"; import { hasRequiredFeature } from "metabase/databases"; import { - type HelpText, expressionModeSupportsClause, getClauseDefinition, getHelpText, diff --git a/frontend/src/metabase/querying/expressions/string.ts b/frontend/src/metabase/querying/expressions/string.ts index 5d2497a0c01..9f2a090a54a 100644 --- a/frontend/src/metabase/querying/expressions/string.ts +++ b/frontend/src/metabase/querying/expressions/string.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars -- used for types */ const DOUBLE_QUOTE = '"'; const SINGLE_QUOTE = "'"; const OPEN_BRACKET = "["; diff --git a/frontend/src/metabase/redux/context.tsx b/frontend/src/metabase/redux/context.tsx index cd257a840d5..05752ff78cf 100644 --- a/frontend/src/metabase/redux/context.tsx +++ b/frontend/src/metabase/redux/context.tsx @@ -3,7 +3,7 @@ import { type PropsWithChildren, createContext } from "react"; import { Provider, type ReactReduxContextValue, - // eslint-disable-next-line no-restricted-imports + // eslint-disable-next-line eslint-js/no-restricted-imports connect as _connect, } from "react-redux"; diff --git a/frontend/src/metabase/search/components/DropdownSidebarFilter/DropdownSidebarFilter.styled.tsx b/frontend/src/metabase/search/components/DropdownSidebarFilter/DropdownSidebarFilter.styled.tsx index 6cb79fbaddc..e3b3e79fb68 100644 --- a/frontend/src/metabase/search/components/DropdownSidebarFilter/DropdownSidebarFilter.styled.tsx +++ b/frontend/src/metabase/search/components/DropdownSidebarFilter/DropdownSidebarFilter.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { EventSandbox } from "metabase/common/components/EventSandbox"; diff --git a/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/SearchFilterPopoverWrapper.styled.tsx b/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/SearchFilterPopoverWrapper.styled.tsx index 056c7c5311e..d6508bb679b 100644 --- a/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/SearchFilterPopoverWrapper.styled.tsx +++ b/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/SearchFilterPopoverWrapper.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Stack } from "metabase/ui"; diff --git a/frontend/src/metabase/search/containers/SearchApp.styled.tsx b/frontend/src/metabase/search/containers/SearchApp.styled.tsx index 73751f1eb8c..065dad9a74d 100644 --- a/frontend/src/metabase/search/containers/SearchApp.styled.tsx +++ b/frontend/src/metabase/search/containers/SearchApp.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { diff --git a/frontend/src/metabase/segments/components/SegmentEditor/DataStep/DataStep.tsx b/frontend/src/metabase/segments/components/SegmentEditor/DataStep/DataStep.tsx index 5eba5aa00f6..1d0d770c530 100644 --- a/frontend/src/metabase/segments/components/SegmentEditor/DataStep/DataStep.tsx +++ b/frontend/src/metabase/segments/components/SegmentEditor/DataStep/DataStep.tsx @@ -6,8 +6,8 @@ import { DataPickerModal, getDataPickerValue, } from "metabase/common/components/Pickers/DataPicker"; -import { TableBreadcrumbs } from "metabase/metadata/components"; import { selectMetadataProvider } from "metabase/metadata-store"; +import { TableBreadcrumbs } from "metabase/metadata/components"; import { useDispatch, useStore } from "metabase/redux"; import { fetchTableMetadataAndForeignKeys } from "metabase/redux/tables"; import { Box, Button, Flex, Icon, Text } from "metabase/ui"; diff --git a/frontend/src/metabase/static-viz/index.tsx b/frontend/src/metabase/static-viz/index.tsx index e5a9b234674..aa98aeab0af 100644 --- a/frontend/src/metabase/static-viz/index.tsx +++ b/frontend/src/metabase/static-viz/index.tsx @@ -1,9 +1,7 @@ import { setPlatformAPI } from "echarts/core"; import ReactDOMServer from "react-dom/server"; -// eslint-disable-next-line import/order import enterpriseOverrides from "ee-overrides"; - import { StaticChoropleth, getStaticChoroplethSettings, diff --git a/frontend/src/metabase/static-viz/polyfill.ts b/frontend/src/metabase/static-viz/polyfill.ts index 073516cfe3f..6d1118e2d24 100644 --- a/frontend/src/metabase/static-viz/polyfill.ts +++ b/frontend/src/metabase/static-viz/polyfill.ts @@ -1,5 +1,4 @@ import "fast-text-encoding"; - import ResizeObserver from "resize-observer-polyfill"; const defineGlobal = (name: PropertyKey, value: unknown): void => { diff --git a/frontend/src/metabase/status/components/PublicStatusListing/PublicStatusListing.styled.tsx b/frontend/src/metabase/status/components/PublicStatusListing/PublicStatusListing.styled.tsx index d10063b1d14..239e30b25d4 100644 --- a/frontend/src/metabase/status/components/PublicStatusListing/PublicStatusListing.styled.tsx +++ b/frontend/src/metabase/status/components/PublicStatusListing/PublicStatusListing.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const PublicStatusListingRoot = styled.div` diff --git a/frontend/src/metabase/status/components/StatusLarge/StatusLarge.styled.tsx b/frontend/src/metabase/status/components/StatusLarge/StatusLarge.styled.tsx index f26592c64c6..01a193ae061 100644 --- a/frontend/src/metabase/status/components/StatusLarge/StatusLarge.styled.tsx +++ b/frontend/src/metabase/status/components/StatusLarge/StatusLarge.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { IconButtonWrapper } from "metabase/common/components/IconButtonWrapper"; diff --git a/frontend/src/metabase/status/components/StatusListing/StatusListing.styled.tsx b/frontend/src/metabase/status/components/StatusListing/StatusListing.styled.tsx index 5406b145654..b865fcaf7de 100644 --- a/frontend/src/metabase/status/components/StatusListing/StatusListing.styled.tsx +++ b/frontend/src/metabase/status/components/StatusListing/StatusListing.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const StatusListingRoot = styled.div` diff --git a/frontend/src/metabase/styled-components/containers/GlobalStyles/GlobalStyles.tsx b/frontend/src/metabase/styled-components/containers/GlobalStyles/GlobalStyles.tsx index c66edce11e2..ce8832655d7 100644 --- a/frontend/src/metabase/styled-components/containers/GlobalStyles/GlobalStyles.tsx +++ b/frontend/src/metabase/styled-components/containers/GlobalStyles/GlobalStyles.tsx @@ -1,14 +1,14 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { Global, css } from "@emotion/react"; import { useMemo } from "react"; import { baseStyle, rootStyle } from "metabase/css/core/base.styled"; import { defaultFontFiles } from "metabase/css/core/fonts.styled"; +import { isEmbeddingSdk } from "metabase/embedding-sdk/config"; import { isPublicEmbedding, isStaticEmbedding, } from "metabase/embedding/config"; -import { isEmbeddingSdk } from "metabase/embedding-sdk/config"; import { useSelector } from "metabase/redux"; import { useSetting } from "metabase/settings"; import { getMetabaseCssVariables } from "metabase/styled-components/theme/css-variables"; diff --git a/frontend/src/metabase/styled-components/theme/css-variables.ts b/frontend/src/metabase/styled-components/theme/css-variables.ts index 5e28ec8fb5d..06c71034499 100644 --- a/frontend/src/metabase/styled-components/theme/css-variables.ts +++ b/frontend/src/metabase/styled-components/theme/css-variables.ts @@ -1,7 +1,7 @@ // WARNING: This file is referenced by CssVarsDeclarationPlugin. // If you move or rename it, update the path in css-vars-declaration-plugin.js. -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; import { getIn } from "icepick"; diff --git a/frontend/src/metabase/timelines/collections/components/EventCard/EventCard.styled.tsx b/frontend/src/metabase/timelines/collections/components/EventCard/EventCard.styled.tsx index 97b9a647b10..837f69a06ea 100644 --- a/frontend/src/metabase/timelines/collections/components/EventCard/EventCard.styled.tsx +++ b/frontend/src/metabase/timelines/collections/components/EventCard/EventCard.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Markdown } from "metabase/common/components/Markdown"; diff --git a/frontend/src/metabase/timelines/collections/components/EventList/EventList.styled.tsx b/frontend/src/metabase/timelines/collections/components/EventList/EventList.styled.tsx index a3fa592b6f6..802cd5f6bc1 100644 --- a/frontend/src/metabase/timelines/collections/components/EventList/EventList.styled.tsx +++ b/frontend/src/metabase/timelines/collections/components/EventList/EventList.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Icon } from "metabase/ui"; diff --git a/frontend/src/metabase/timelines/collections/components/LoadingAndErrorWrapper/LoadingAndErrorWrapper.styled.tsx b/frontend/src/metabase/timelines/collections/components/LoadingAndErrorWrapper/LoadingAndErrorWrapper.styled.tsx index 15be17bdf0a..0db3fd50d3f 100644 --- a/frontend/src/metabase/timelines/collections/components/LoadingAndErrorWrapper/LoadingAndErrorWrapper.styled.tsx +++ b/frontend/src/metabase/timelines/collections/components/LoadingAndErrorWrapper/LoadingAndErrorWrapper.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { LoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErrorWrapper"; diff --git a/frontend/src/metabase/timelines/collections/components/TimelineCard/TimelineCard.styled.tsx b/frontend/src/metabase/timelines/collections/components/TimelineCard/TimelineCard.styled.tsx index 3618ada531c..04a97606d60 100644 --- a/frontend/src/metabase/timelines/collections/components/TimelineCard/TimelineCard.styled.tsx +++ b/frontend/src/metabase/timelines/collections/components/TimelineCard/TimelineCard.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Link } from "metabase/common/components/Link"; diff --git a/frontend/src/metabase/timelines/collections/components/TimelineDetailsModal/TimelineDetailsModal.styled.tsx b/frontend/src/metabase/timelines/collections/components/TimelineDetailsModal/TimelineDetailsModal.styled.tsx index 4349a276398..3646c7fc733 100644 --- a/frontend/src/metabase/timelines/collections/components/TimelineDetailsModal/TimelineDetailsModal.styled.tsx +++ b/frontend/src/metabase/timelines/collections/components/TimelineDetailsModal/TimelineDetailsModal.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const ModalRoot = styled.div` diff --git a/frontend/src/metabase/timelines/collections/components/TimelineListModal/TimelineListModal.styled.tsx b/frontend/src/metabase/timelines/collections/components/TimelineListModal/TimelineListModal.styled.tsx index 6dc8331a7dd..f324c91c264 100644 --- a/frontend/src/metabase/timelines/collections/components/TimelineListModal/TimelineListModal.styled.tsx +++ b/frontend/src/metabase/timelines/collections/components/TimelineListModal/TimelineListModal.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const ModalRoot = styled.div` diff --git a/frontend/src/metabase/timelines/common/components/ModalFooter/ModalFooter.styled.tsx b/frontend/src/metabase/timelines/common/components/ModalFooter/ModalFooter.styled.tsx index d7554d76f11..9bb1a5a05c4 100644 --- a/frontend/src/metabase/timelines/common/components/ModalFooter/ModalFooter.styled.tsx +++ b/frontend/src/metabase/timelines/common/components/ModalFooter/ModalFooter.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; interface FooterProps { diff --git a/frontend/src/metabase/timelines/common/components/ModalHeader/ModalHeader.styled.tsx b/frontend/src/metabase/timelines/common/components/ModalHeader/ModalHeader.styled.tsx index d250fa4808a..23e94e05c56 100644 --- a/frontend/src/metabase/timelines/common/components/ModalHeader/ModalHeader.styled.tsx +++ b/frontend/src/metabase/timelines/common/components/ModalHeader/ModalHeader.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { IconButtonWrapper } from "metabase/common/components/IconButtonWrapper"; diff --git a/frontend/src/metabase/timelines/common/components/MoveEventModal/MoveEventModal.styled.tsx b/frontend/src/metabase/timelines/common/components/MoveEventModal/MoveEventModal.styled.tsx index 0f0064d54e9..1f7166849a6 100644 --- a/frontend/src/metabase/timelines/common/components/MoveEventModal/MoveEventModal.styled.tsx +++ b/frontend/src/metabase/timelines/common/components/MoveEventModal/MoveEventModal.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const ModalRoot = styled.div` diff --git a/frontend/src/metabase/timelines/common/components/TimelineForm/TimelineForm.styled.tsx b/frontend/src/metabase/timelines/common/components/TimelineForm/TimelineForm.styled.tsx index 5a73a010649..9f45c1c5f59 100644 --- a/frontend/src/metabase/timelines/common/components/TimelineForm/TimelineForm.styled.tsx +++ b/frontend/src/metabase/timelines/common/components/TimelineForm/TimelineForm.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const TimelineFormFooter = styled.div` diff --git a/frontend/src/metabase/timelines/common/components/TimelinePicker/TimelinePicker.styled.tsx b/frontend/src/metabase/timelines/common/components/TimelinePicker/TimelinePicker.styled.tsx index 6216114ee30..e541b1349bc 100644 --- a/frontend/src/metabase/timelines/common/components/TimelinePicker/TimelinePicker.styled.tsx +++ b/frontend/src/metabase/timelines/common/components/TimelinePicker/TimelinePicker.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Icon } from "metabase/ui"; diff --git a/frontend/src/metabase/timelines/questions/components/EventCard/EventCard.styled.tsx b/frontend/src/metabase/timelines/questions/components/EventCard/EventCard.styled.tsx index 0f27c6f59aa..dee55c72821 100644 --- a/frontend/src/metabase/timelines/questions/components/EventCard/EventCard.styled.tsx +++ b/frontend/src/metabase/timelines/questions/components/EventCard/EventCard.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { alpha } from "metabase/ui/colors"; diff --git a/frontend/src/metabase/transforms/pages/TransformListPage/TransformListPage.tsx b/frontend/src/metabase/transforms/pages/TransformListPage/TransformListPage.tsx index 62dbc6324d9..f1835a1628c 100644 --- a/frontend/src/metabase/transforms/pages/TransformListPage/TransformListPage.tsx +++ b/frontend/src/metabase/transforms/pages/TransformListPage/TransformListPage.tsx @@ -24,9 +24,9 @@ import { LockedTransformsBanner } from "metabase/transforms/components/LockedTra import { TransformsHeader } from "metabase/transforms/components/TransformsHeader"; import { useTransformPermissions } from "metabase/transforms/hooks/use-transform-permissions"; import { getShouldShowPythonTransformsUpsell } from "metabase/transforms/selectors"; -import { Ellipsified } from "metabase/ui"; import { Card, + Ellipsified, EntityNameCell, Flex, Group, diff --git a/frontend/src/metabase/ui/components/data-display/Timeline/Timeline.stories.tsx b/frontend/src/metabase/ui/components/data-display/Timeline/Timeline.stories.tsx index a53e743e207..f81b05a2b5c 100644 --- a/frontend/src/metabase/ui/components/data-display/Timeline/Timeline.stories.tsx +++ b/frontend/src/metabase/ui/components/data-display/Timeline/Timeline.stories.tsx @@ -1,5 +1,4 @@ -import { Timeline, type TimelineProps } from "metabase/ui"; -import { Box, Text } from "metabase/ui"; +import { Box, Text, Timeline, type TimelineProps } from "metabase/ui"; const args = { active: 1, diff --git a/frontend/src/metabase/ui/components/feedback/Skeleton/Repeat.tsx b/frontend/src/metabase/ui/components/feedback/Skeleton/Repeat.tsx index 5c1e6544b36..1500b797256 100644 --- a/frontend/src/metabase/ui/components/feedback/Skeleton/Repeat.tsx +++ b/frontend/src/metabase/ui/components/feedback/Skeleton/Repeat.tsx @@ -1,5 +1,4 @@ -import { type PropsWithChildren, isValidElement } from "react"; -import { cloneElement } from "react"; +import { type PropsWithChildren, cloneElement, isValidElement } from "react"; export const Repeat = ({ times, diff --git a/frontend/src/metabase/ui/components/icons/Icon/Icon.tsx b/frontend/src/metabase/ui/components/icons/Icon/Icon.tsx index 9d9bf46c1a5..acfe6b5815c 100644 --- a/frontend/src/metabase/ui/components/icons/Icon/Icon.tsx +++ b/frontend/src/metabase/ui/components/icons/Icon/Icon.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { Box, type BoxProps, type FloatingPosition } from "@mantine/core"; import cx from "classnames"; diff --git a/frontend/src/metabase/ui/components/icons/Icon/icons/index.ts b/frontend/src/metabase/ui/components/icons/Icon/icons/index.ts index 84d39b43e52..73f1bffbf89 100644 --- a/frontend/src/metabase/ui/components/icons/Icon/icons/index.ts +++ b/frontend/src/metabase/ui/components/icons/Icon/icons/index.ts @@ -1,7 +1,7 @@ import type { IconName } from "metabase-types/api"; -import ten_thousand_component from "./10k.svg?component"; import one_million_component from "./1m.svg?component"; +import ten_thousand_component from "./10k.svg?component"; import add_component from "./add.svg?component"; import add_collection_component from "./add_collection.svg?component"; import add_column_component from "./add_column.svg?component"; diff --git a/frontend/src/metabase/ui/components/theme/EmotionCacheProvider/EmotionCacheProvider.tsx b/frontend/src/metabase/ui/components/theme/EmotionCacheProvider/EmotionCacheProvider.tsx index 36787d30a47..8dd32b7b801 100644 --- a/frontend/src/metabase/ui/components/theme/EmotionCacheProvider/EmotionCacheProvider.tsx +++ b/frontend/src/metabase/ui/components/theme/EmotionCacheProvider/EmotionCacheProvider.tsx @@ -1,5 +1,5 @@ import createCache from "@emotion/cache"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { CacheProvider } from "@emotion/react"; import type { ReactNode } from "react"; import { useMemo } from "react"; diff --git a/frontend/src/metabase/ui/components/theme/ThemeProvider/ThemeProvider.tsx b/frontend/src/metabase/ui/components/theme/ThemeProvider/ThemeProvider.tsx index 73b7678426c..69a7c86b14b 100644 --- a/frontend/src/metabase/ui/components/theme/ThemeProvider/ThemeProvider.tsx +++ b/frontend/src/metabase/ui/components/theme/ThemeProvider/ThemeProvider.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { ThemeProvider as _CompatibilityEmotionThemeProvider } from "@emotion/react"; import type { MantineTheme, MantineThemeOverride } from "@mantine/core"; import { MantineProvider } from "@mantine/core"; diff --git a/frontend/src/metabase/value-formatting/date.unit.spec.tsx b/frontend/src/metabase/value-formatting/date.unit.spec.tsx index 81d5805db1d..fd40183cb2c 100644 --- a/frontend/src/metabase/value-formatting/date.unit.spec.tsx +++ b/frontend/src/metabase/value-formatting/date.unit.spec.tsx @@ -46,7 +46,7 @@ describe("formatDateTimeRangeWithUnit", () => { ); if (verboseOutput) { - // eslint-disable-next-line jest/no-conditional-expect + // eslint-disable-next-line jest-js/no-conditional-expect expect(formatDateTimeRangeWithUnit(input, unit)).toBe( verboseOutput, ); diff --git a/frontend/src/metabase/visualizations/components/ChartCaption.styled.tsx b/frontend/src/metabase/visualizations/components/ChartCaption.styled.tsx index 26ae01c5e96..59e60f75ca4 100644 --- a/frontend/src/metabase/visualizations/components/ChartCaption.styled.tsx +++ b/frontend/src/metabase/visualizations/components/ChartCaption.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { LegendCaption } from "./legend/LegendCaption"; diff --git a/frontend/src/metabase/visualizations/components/EChartsRenderer/EChartsRenderer.styled.tsx b/frontend/src/metabase/visualizations/components/EChartsRenderer/EChartsRenderer.styled.tsx index d12e41f6db1..9c2d2d6e8fc 100644 --- a/frontend/src/metabase/visualizations/components/EChartsRenderer/EChartsRenderer.styled.tsx +++ b/frontend/src/metabase/visualizations/components/EChartsRenderer/EChartsRenderer.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const EChartsRendererRoot = styled.div` diff --git a/frontend/src/metabase/visualizations/components/EChartsRenderer/ResponsiveEChartsRenderer.styled.tsx b/frontend/src/metabase/visualizations/components/EChartsRenderer/ResponsiveEChartsRenderer.styled.tsx index 9da5f089420..0b459ac1337 100644 --- a/frontend/src/metabase/visualizations/components/EChartsRenderer/ResponsiveEChartsRenderer.styled.tsx +++ b/frontend/src/metabase/visualizations/components/EChartsRenderer/ResponsiveEChartsRenderer.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { ResponsiveEChartsRendererExplicitSize } from "./ResponsiveEChartsRenderer"; diff --git a/frontend/src/metabase/visualizations/components/FunnelNormal.styled.tsx b/frontend/src/metabase/visualizations/components/FunnelNormal.styled.tsx index 5be2732f2b5..671b70a3aaa 100644 --- a/frontend/src/metabase/visualizations/components/FunnelNormal.styled.tsx +++ b/frontend/src/metabase/visualizations/components/FunnelNormal.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { isWebkit } from "metabase/utils/browser"; diff --git a/frontend/src/metabase/visualizations/components/LeafletMap.tsx b/frontend/src/metabase/visualizations/components/LeafletMap.tsx index 824a61eb49e..021817b81c1 100644 --- a/frontend/src/metabase/visualizations/components/LeafletMap.tsx +++ b/frontend/src/metabase/visualizations/components/LeafletMap.tsx @@ -1,7 +1,6 @@ import "leaflet-draw"; import "leaflet/dist/leaflet.css"; import "./LeafletMap.module.css"; - import L from "leaflet"; import { Component, createRef } from "react"; import _ from "underscore"; diff --git a/frontend/src/metabase/visualizations/components/LeafletTilePinMap.tsx b/frontend/src/metabase/visualizations/components/LeafletTilePinMap.tsx index 4dfcb0270a6..78d9507c85d 100644 --- a/frontend/src/metabase/visualizations/components/LeafletTilePinMap.tsx +++ b/frontend/src/metabase/visualizations/components/LeafletTilePinMap.tsx @@ -2,8 +2,8 @@ import L from "leaflet"; import type { ContextType } from "react"; import { api } from "metabase/api/client"; -import { EmbeddingEntityContext } from "metabase/embedding/context"; import { isEmbeddingSdk } from "metabase/embedding-sdk/config"; +import { EmbeddingEntityContext } from "metabase/embedding/context"; import { isWithinIframe } from "metabase/utils/iframe"; import type { DashboardId } from "metabase-types/api"; diff --git a/frontend/src/metabase/visualizations/components/ObjectDetail/ObjectDetailPanel.styled.tsx b/frontend/src/metabase/visualizations/components/ObjectDetail/ObjectDetailPanel.styled.tsx index 5790553354b..fed59a5ae28 100644 --- a/frontend/src/metabase/visualizations/components/ObjectDetail/ObjectDetailPanel.styled.tsx +++ b/frontend/src/metabase/visualizations/components/ObjectDetail/ObjectDetailPanel.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; interface ObjectDetailContainerProps { diff --git a/frontend/src/metabase/visualizations/components/legend/Legend.styled.tsx b/frontend/src/metabase/visualizations/components/legend/Legend.styled.tsx index b07f3a8acab..ecb41d7f42f 100644 --- a/frontend/src/metabase/visualizations/components/legend/Legend.styled.tsx +++ b/frontend/src/metabase/visualizations/components/legend/Legend.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { darken } from "metabase/ui/colors"; diff --git a/frontend/src/metabase/visualizations/components/legend/LegendActions.styled.tsx b/frontend/src/metabase/visualizations/components/legend/LegendActions.styled.tsx index 8fbfbfc2cb6..a10725b4403 100644 --- a/frontend/src/metabase/visualizations/components/legend/LegendActions.styled.tsx +++ b/frontend/src/metabase/visualizations/components/legend/LegendActions.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const LegendActionsRoot = styled.div` diff --git a/frontend/src/metabase/visualizations/components/legend/LegendCaption/LegendCaption.styled.tsx b/frontend/src/metabase/visualizations/components/legend/LegendCaption/LegendCaption.styled.tsx index 1b8d550fae6..979eff65365 100644 --- a/frontend/src/metabase/visualizations/components/legend/LegendCaption/LegendCaption.styled.tsx +++ b/frontend/src/metabase/visualizations/components/legend/LegendCaption/LegendCaption.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { forwardRef } from "react"; diff --git a/frontend/src/metabase/visualizations/components/legend/LegendItem.styled.tsx b/frontend/src/metabase/visualizations/components/legend/LegendItem.styled.tsx index dfb94716fce..119badbcb42 100644 --- a/frontend/src/metabase/visualizations/components/legend/LegendItem.styled.tsx +++ b/frontend/src/metabase/visualizations/components/legend/LegendItem.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const LegendItemRoot = styled.div<{ isVertical: boolean }>` diff --git a/frontend/src/metabase/visualizations/components/legend/LegendItemDot.styled.tsx b/frontend/src/metabase/visualizations/components/legend/LegendItemDot.styled.tsx index ae0643c1070..6ef404cb4bc 100644 --- a/frontend/src/metabase/visualizations/components/legend/LegendItemDot.styled.tsx +++ b/frontend/src/metabase/visualizations/components/legend/LegendItemDot.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const OuterCircle = styled.div` diff --git a/frontend/src/metabase/visualizations/components/legend/LegendLayout.styled.tsx b/frontend/src/metabase/visualizations/components/legend/LegendLayout.styled.tsx index f012c850bcf..c0fe4197233 100644 --- a/frontend/src/metabase/visualizations/components/legend/LegendLayout.styled.tsx +++ b/frontend/src/metabase/visualizations/components/legend/LegendLayout.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const LegendLayoutRoot = styled.div<{ isVertical: boolean }>` diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingNestedSettings.tsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingNestedSettings.tsx index 672ef79aec3..13e89e7534c 100644 --- a/frontend/src/metabase/visualizations/components/settings/ChartSettingNestedSettings.tsx +++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingNestedSettings.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/display-name */ import { Component, type ComponentType } from "react"; import _ from "underscore"; diff --git a/frontend/src/metabase/visualizations/visualizations/CartesianChart/CartesianChart.styled.tsx b/frontend/src/metabase/visualizations/visualizations/CartesianChart/CartesianChart.styled.tsx index 2cfefba14db..45e21c1aaaf 100644 --- a/frontend/src/metabase/visualizations/visualizations/CartesianChart/CartesianChart.styled.tsx +++ b/frontend/src/metabase/visualizations/visualizations/CartesianChart/CartesianChart.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { LegendLayout } from "metabase/visualizations/components/legend/LegendLayout"; diff --git a/frontend/src/metabase/visualizations/visualizations/CartesianChart/CartesianChart.tsx b/frontend/src/metabase/visualizations/visualizations/CartesianChart/CartesianChart.tsx index dfef608b149..71c1263a9f8 100644 --- a/frontend/src/metabase/visualizations/visualizations/CartesianChart/CartesianChart.tsx +++ b/frontend/src/metabase/visualizations/visualizations/CartesianChart/CartesianChart.tsx @@ -1,6 +1,11 @@ import type { EChartsType } from "echarts/core"; -import { type MouseEvent, useCallback, useMemo, useRef, useState } from "react"; -import React from "react"; +import React, { + type MouseEvent, + useCallback, + useMemo, + useRef, + useState, +} from "react"; import { useSet } from "react-use"; import { isWebkit } from "metabase/utils/browser"; diff --git a/frontend/src/metabase/visualizations/visualizations/Map/Maps.styled.tsx b/frontend/src/metabase/visualizations/visualizations/Map/Maps.styled.tsx index d9c1115fb7e..81728d162b3 100644 --- a/frontend/src/metabase/visualizations/visualizations/Map/Maps.styled.tsx +++ b/frontend/src/metabase/visualizations/visualizations/Map/Maps.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; export const CustomMapContent = styled.div` diff --git a/frontend/src/metabase/visualizations/visualizations/PivotTable/PivotTable.styled.tsx b/frontend/src/metabase/visualizations/visualizations/PivotTable/PivotTable.styled.tsx index b4972953fb1..0358df4644d 100644 --- a/frontend/src/metabase/visualizations/visualizations/PivotTable/PivotTable.styled.tsx +++ b/frontend/src/metabase/visualizations/visualizations/PivotTable/PivotTable.styled.tsx @@ -1,6 +1,6 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import type { MantineTheme } from "metabase/ui"; diff --git a/frontend/src/metabase/visualizations/visualizations/RowChart/RowChart.styled.tsx b/frontend/src/metabase/visualizations/visualizations/RowChart/RowChart.styled.tsx index 512416948bc..f6919b7f6b1 100644 --- a/frontend/src/metabase/visualizations/visualizations/RowChart/RowChart.styled.tsx +++ b/frontend/src/metabase/visualizations/visualizations/RowChart/RowChart.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { LegendCaption } from "metabase/visualizations/components/legend/LegendCaption"; diff --git a/frontend/src/metabase/visualizations/visualizations/SmartScalar/SettingsComponents/PeriodsAgoMenuOption.styled.tsx b/frontend/src/metabase/visualizations/visualizations/SmartScalar/SettingsComponents/PeriodsAgoMenuOption.styled.tsx index 7f9608cdeb2..4e3337a0395 100644 --- a/frontend/src/metabase/visualizations/visualizations/SmartScalar/SettingsComponents/PeriodsAgoMenuOption.styled.tsx +++ b/frontend/src/metabase/visualizations/visualizations/SmartScalar/SettingsComponents/PeriodsAgoMenuOption.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import { NumberInput } from "metabase/ui"; diff --git a/frontend/src/metabase/visualizations/visualizations/SmartScalar/SettingsComponents/SmartScalarSettingsWidgets.styled.tsx b/frontend/src/metabase/visualizations/visualizations/SmartScalar/SettingsComponents/SmartScalarSettingsWidgets.styled.tsx index b024e234d6d..4cc44e70055 100644 --- a/frontend/src/metabase/visualizations/visualizations/SmartScalar/SettingsComponents/SmartScalarSettingsWidgets.styled.tsx +++ b/frontend/src/metabase/visualizations/visualizations/SmartScalar/SettingsComponents/SmartScalarSettingsWidgets.styled.tsx @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import styled from "@emotion/styled"; import cx from "classnames"; import type { HTMLAttributes } from "react"; diff --git a/frontend/src/metabase/visualizer/components/DataImporter/ColumnsList/ColumnsListItem.tsx b/frontend/src/metabase/visualizer/components/DataImporter/ColumnsList/ColumnsListItem.tsx index 9a6ce692b3b..540b50471c2 100644 --- a/frontend/src/metabase/visualizer/components/DataImporter/ColumnsList/ColumnsListItem.tsx +++ b/frontend/src/metabase/visualizer/components/DataImporter/ColumnsList/ColumnsListItem.tsx @@ -6,8 +6,13 @@ import { } from "react"; import { t } from "ttag"; -import { Ellipsified } from "metabase/ui"; -import { ActionIcon, Flex, type FlexProps, Icon } from "metabase/ui"; +import { + ActionIcon, + Ellipsified, + Flex, + type FlexProps, + Icon, +} from "metabase/ui"; import { displayNameForColumn } from "metabase/value-formatting"; import { getIconForField } from "metabase-lib/v1/metadata/utils/fields"; import type { DatasetColumn, IconName } from "metabase-types/api"; diff --git a/frontend/src/metabase/viz-core/lib/image-exports.ts b/frontend/src/metabase/viz-core/lib/image-exports.ts index db6fee59b7b..619ebe3584a 100644 --- a/frontend/src/metabase/viz-core/lib/image-exports.ts +++ b/frontend/src/metabase/viz-core/lib/image-exports.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-restricted-imports +// eslint-disable-next-line eslint-js/no-restricted-imports import { css } from "@emotion/react"; import GlobalDashboardS from "metabase/css/dashboard.module.css"; diff --git a/frontend/src/types/emotion.d.ts b/frontend/src/types/emotion.d.ts index 3287d22bc3f..b4eecf824d1 100644 --- a/frontend/src/types/emotion.d.ts +++ b/frontend/src/types/emotion.d.ts @@ -1,5 +1,4 @@ import "@emotion/react"; - import type { MantineTheme } from "@mantine/core"; interface _EmotionCompatibilityTheme { fn: { themeColor: (colorName: string) => string }; diff --git a/frontend/src/types/mantine.d.ts b/frontend/src/types/mantine.d.ts index a9062561fa5..077b91073e4 100644 --- a/frontend/src/types/mantine.d.ts +++ b/frontend/src/types/mantine.d.ts @@ -26,7 +26,7 @@ declare module "@mantine/core" { updateColorSettings: (settings: ColorSettings) => void; } - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- module augment + // eslint-disable-next-line typescript-js/no-empty-object-type -- module augment export interface MantineTheme extends _EmotionCompatibilityTheme {} export interface MantineThemeColorsOverride { diff --git a/frontend/src/types/react.d.ts b/frontend/src/types/react.d.ts index 83f02d98cd2..77c37d4588e 100644 --- a/frontend/src/types/react.d.ts +++ b/frontend/src/types/react.d.ts @@ -2,7 +2,7 @@ import type * as React from "react"; declare module "react" { // to make forwardRef work with generic function components - // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- generic default + // eslint-disable-next-line typescript-js/no-empty-object-type -- generic default function forwardRef( render: (props: P, ref: React.Ref) => React.ReactElement | null, ): (props: P & React.RefAttributes) => React.ReactElement | null; diff --git a/frontend/test/__support__/mocks.js b/frontend/test/__support__/mocks.js index 7135ef907cc..2135d89b334 100644 --- a/frontend/test/__support__/mocks.js +++ b/frontend/test/__support__/mocks.js @@ -59,6 +59,8 @@ jest.mock("@uiw/react-codemirror", () => { ); }); + MockEditor.displayName = "MockEditor"; + return { __esModule: true, ...jest.requireActual("@uiw/react-codemirror"), diff --git a/frontend/test/__support__/ui.tsx b/frontend/test/__support__/ui.tsx index 785771ceecf..550aae47a38 100644 --- a/frontend/test/__support__/ui.tsx +++ b/frontend/test/__support__/ui.tsx @@ -684,8 +684,8 @@ export function renderWithTheme(children: React.ReactElement) { }); } -// eslint-disable-next-line import/export -- intentionally overriding render from @testing-library/react +// eslint-disable-next-line import-js/export -- intentionally overriding render from @testing-library/react export { renderWithTheme as render }; -// eslint-disable-next-line import/export -- intentionally overriding render from @testing-library/react +// eslint-disable-next-line import-js/export -- intentionally overriding render from @testing-library/react export * from "@testing-library/react"; diff --git a/frontend/test/jest-setup-eager.js b/frontend/test/jest-setup-eager.js index 3069c257983..24527944b30 100644 --- a/frontend/test/jest-setup-eager.js +++ b/frontend/test/jest-setup-eager.js @@ -6,12 +6,10 @@ // once the run has started. import "@testing-library/react"; import "@testing-library/user-event"; - // leaflet-draw reads `window.L`, which leaflet sets when it runs. The app // imports leaflet-draw before leaflet and relies on leaflet having run // earlier, which no longer happens on its own. import "leaflet"; - // It snapshots `window.MetabaseBootstrap` when it runs. Specs expect the // bootstrap from `metabase-bootstrap.js`, not the one the core `beforeEach` // merges mock settings into. diff --git a/jest.esm-packages.js b/jest.esm-packages.js index 88de16f825e..1be9a4400dc 100644 --- a/jest.esm-packages.js +++ b/jest.esm-packages.js @@ -43,6 +43,7 @@ const esmPackages = [ "marked", "mdast.*", "micromark.*", + "oxlint", "parse-entities", "property-information", "react-markdown", diff --git a/lint-staged.config.cjs b/lint-staged.config.cjs index e3fcc22e92e..70a00edd436 100644 --- a/lint-staged.config.cjs +++ b/lint-staged.config.cjs @@ -1,3 +1,6 @@ +const lintFix = + "oxlint --disable-nested-config --no-error-on-unmatched-pattern --max-warnings 0 --report-unused-disable-directives --fix"; + module.exports = { "+(frontend|enterprise)/**/*.styled.tsx": [ "stylelint --customSyntax postcss-styled-syntax --fix", @@ -7,12 +10,12 @@ module.exports = { "oxfmt --write", ], "+(frontend|enterprise/frontend)/**/*.{js,jsx,ts,tsx}": [ - "cross-env LINT_CSS_MODULES=true eslint --no-warn-ignored --max-warnings 0 --fix", + `cross-env LINT_CSS_MODULES=true ${lintFix}`, "oxfmt --write", "node ./bin/verify-doc-links", ], "e2e/**/!(cypress_sample_instance_data).{js,jsx,ts,tsx}": [ - "eslint --no-warn-ignored --max-warnings 0 --fix", + lintFix, "oxfmt --write", ], "**/*.{clj,cljc,cljs,bb}": [ @@ -20,9 +23,7 @@ module.exports = { "./bin/mage fix-unused-requires", ], "e2e/test/scenarios/**/*.{js,ts}": ["node e2e/validate-e2e-test-files.js"], - "enterprise/frontend/src/embedding-sdk-package/README.md": [ - "oxfmt --write", - ], + "enterprise/frontend/src/embedding-sdk-package/README.md": ["oxfmt --write"], "+(.storybook|enterprise/frontend/src/embedding-sdk-shared/.storybook)/**/*.{js,jsx,ts,tsx,css}": ["oxfmt --write"], "**/*": [ diff --git a/mage/src/mage/util.clj b/mage/src/mage/util.clj index d2219258baf..e6176060692 100644 --- a/mage/src/mage/util.clj +++ b/mage/src/mage/util.clj @@ -152,7 +152,6 @@ ;; Clear the throbber when done (print "\r") (flush)) - ;; Execute the function (try (let [res (f)] diff --git a/oxlint.config.mts b/oxlint.config.mts new file mode 100644 index 00000000000..600af7067c8 --- /dev/null +++ b/oxlint.config.mts @@ -0,0 +1,7 @@ +import { defineConfig } from "oxlint"; + +import { createConfig } from "./frontend/lint/oxlint/config.mjs"; + +// Oxlint requires a default config export. +// eslint-disable-next-line import/no-default-export +export default defineConfig(createConfig()); diff --git a/package.json b/package.json index 2a1e7aeb90b..dbfab58ecd3 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "mustache": "^2.3.2", "normalizr": "^3.0.2", "ora": "^8.0.1", - "oxlint": "^1.58.0", + "oxlint": "1.85.0", "prettier": "^3.3.3", "process": "^0.11.10", "prosemirror-dropcursor": "1.8.2", @@ -250,6 +250,7 @@ "@types/redux-actions": "^2.6.5", "@types/semver": "^7.7.1", "@types/underscore": "^1.13.0", + "@typescript-eslint/eslint-plugin": "8.60.0", "@vitejs/plugin-react": "^6.0.2", "ajv": "^8.5.0", "babel-loader": "^9.1.2", @@ -281,13 +282,12 @@ "eslint-plugin-boundaries": "5.4.0", "eslint-plugin-chai-friendly": "^1.1.0", "eslint-plugin-cypress": "6.1.0", - "eslint-plugin-depend": "1.4.0", - "eslint-plugin-i18next": "6.1.3", - "eslint-plugin-import-x": "4.16.1", + "eslint-plugin-depend": "1.5.0", + "eslint-plugin-i18next": "6.1.4", + "eslint-plugin-import-x": "4.17.1", "eslint-plugin-jest": "29.12.1", "eslint-plugin-jest-dom": "^5.5.0", - "eslint-plugin-jest-formatting": "^3.1.0", - "eslint-plugin-no-only-tests": "^3.3.0", + "eslint-plugin-no-only-tests": "3.4.0", "eslint-plugin-react": "7.37.5", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-storybook": "9.1.17", @@ -330,6 +330,7 @@ "open-cli": "^8.0.0", "openapi-typescript": "^7.10.0", "otpauth": "^9.5.1", + "oxc-resolver": "11.24.2", "oxfmt": "^0.38.0", "patch-package": "^8.0.1", "pg": "^8.8.0", @@ -375,7 +376,8 @@ "webpack-notifier": "1.15.0", "webpack-stats-plugin": "^1.1.3", "yaml-lint": "~1.6.0", - "zrender": "6.1.0" + "zrender": "6.1.0", + "html-entities": "2.3.2" }, "resolutions": { "@codemirror/autocomplete": "^6.18.6", @@ -467,16 +469,17 @@ "embedding-sdk:docs-snippets:type-check": "./bin/embedding-sdk/type-check-embedding-sdk-docs-snippets.sh", "embedding-sdk:clean:dist": "rm -rf resources/embedding-sdk/dist", "embedding-sdk:clean:tsbuildinfo": "rm -rf resources/embedding-sdk/.tsbuildinfo", - "eslint-fix": "bun run lint-eslint --fix", "find-never-defined-css-variables": "tsx bin/find-never-defined-css-variables/find-never-defined-css-variables.ts", "generate-cypress-html-report": "mochawesome-merge cypress/reports/mochareports/*.json > cypress/reports/cypress-test-report.json && marge cypress/reports/cypress-test-report.json -o cypress/reports --inline", "generate-openapi": "clojure -M:run:ee generate-openapi-spec", "lint-openapi": "redocly lint resources/openapi/openapi.json --skip-rule security-defined --skip-rule operation-operationId --skip-rule no-unused-components --skip-rule info-license --skip-rule no-path-trailing-slash --skip-rule no-identical-paths --skip-rule no-ambiguous-paths --skip-rule path-parameters-defined --skip-rule spec-components-invalid-map-name", - "lint": "bun run lint-eslint && bun run lint-format && bun run lint-docs-links && bun run lint-yaml && bun run type-check", + "lint": "bun run lint-oxlint && bun run lint-format && bun run lint-docs-links && bun run lint-yaml && bun run type-check", "lint-docs-links": "bun install && ./bin/verify-doc-links", "lint-css": "stylelint './frontend/**/*.css'", - "lint-eslint": "bun run build:cljs && bun run lint-eslint-pure", - "lint-eslint-pure": "eslint --cache --cache-strategy content --max-warnings 0 --report-unused-disable-directives enterprise/frontend frontend e2e", + "lint-oxlint": "bun run build:cljs && bun run lint-oxlint-pure", + "lint-oxlint-pure": "oxlint --disable-nested-config --max-warnings 0 --report-unused-disable-directives enterprise/frontend frontend e2e", + "test-oxlint": "node --test frontend/lint/tests/oxlint-*.test.mjs", + "lint-config-update": "node frontend/lint/update-recommended-rules.mjs && oxfmt frontend/lint/recommended-rules.json frontend/lint/oxlint/rule-defaults.json", "module-boundaries": "eslint --config eslint.config.module-boundaries.mjs 'frontend/src/**/*.{ts,tsx}' 'enterprise/frontend/src/**/*.{ts,tsx}'", "lint-format": "bun install && bun lint-format-pure", "lint-format-pure": "oxfmt --check '{frontend,enterprise/frontend,e2e}/**/*.{js,jsx,ts,tsx,css}'", @@ -517,7 +520,8 @@ "wait:cljs": "echo Waiting for first CLJS build; until [ -f target/cljs_dev/cljs.core.js ]; do sleep 1; done; echo CLJS build done", "generate:expression-parser": "lezer-generator --typeScript frontend/src/metabase/querying/expressions/tokenizer/grammar.lezer -o frontend/src/metabase/querying/expressions/tokenizer/lezer.ts", "generate-license-disclaimer": "node bin/generate-license-disclaimer.js", - "cloud:dev": "bun install && clojure -A:dev:drivers:drivers-dev:ee:ee-dev:cljs -P && bun run concurrently -n 'FE,BE' -c 'blue,green' 'bun run build-hot' 'clojure -M:dev:socket:dev-start:drivers:drivers-dev:ee:ee-dev'" + "cloud:dev": "bun install && clojure -A:dev:drivers:drivers-dev:ee:ee-dev:cljs -P && bun run concurrently -n 'FE,BE' -c 'blue,green' 'bun run build-hot' 'clojure -M:dev:socket:dev-start:drivers:drivers-dev:ee:ee-dev'", + "lint-oxlint-fix": "bun run lint-oxlint --fix" }, "browserslist": [ "chrome >= 120", diff --git a/rspack.embedding-sdk-bundle.config.js b/rspack.embedding-sdk-bundle.config.js index d7eb59a9a83..94b55629df5 100644 --- a/rspack.embedding-sdk-bundle.config.js +++ b/rspack.embedding-sdk-bundle.config.js @@ -1,6 +1,5 @@ /* eslint-env node */ /* eslint-disable import/no-commonjs */ -/* eslint-disable import/order */ const NodePolyfillPlugin = require("node-polyfill-webpack-plugin"); const rspack = require("@rspack/core"); const BundleAnalyzerPlugin = @@ -28,9 +27,7 @@ const { CSS_CONFIG } = require("./frontend/build/shared/rspack/css-config"); const { SIDE_EFFECT_FREE_RULE, } = require("./frontend/build/shared/rspack/side-effect-free-modules"); -const { - EXTERNAL_DEPENDENCIES, -} = require("./frontend/build/embedding-sdk/constants/external-dependencies"); +const resolveConfig = require("./frontend/build/embedding-sdk/rspack/resolve-config"); const { getBannerOptions, } = require("./frontend/build/shared/rspack/get-banner-options"); @@ -55,9 +52,6 @@ const SDK_BUNDLE_SRC_PATH = __dirname + "/frontend/src/embedding-sdk-bundle"; const BUILD_PATH = __dirname + "/resources/frontend_client"; const SDK_OUTPUT_PATH = path.join(BUILD_PATH, SDK_BUNDLE_PATH); -const ENTERPRISE_SRC_PATH = - __dirname + "/enterprise/frontend/src/metabase-enterprise"; - const shouldAnalyzeBundles = process.env.SHOULD_ANALYZE_BUNDLES === "true"; // Name prefix for all chunked-entry output files (runtime, split chunks, entry) @@ -207,7 +201,7 @@ const config = { ], }, - externals: EXTERNAL_DEPENDENCIES, + ...resolveConfig, optimization: { ...OPTIMIZATION_CONFIG, @@ -430,14 +424,6 @@ const config = { ].filter(Boolean), }; -config.resolve.alias = { - ...mainConfig.resolve.alias, - "sdk-ee-plugins": ENTERPRISE_SRC_PATH + "/sdk-plugins", - "sdk-iframe-embedding-ee-plugins": - ENTERPRISE_SRC_PATH + "/sdk-iframe-embedding-plugins", - "ee-overrides": ENTERPRISE_SRC_PATH + "/overrides", -}; - if (config.cache) { config.cache.cacheDirectory = resolve( __dirname, diff --git a/rspack.embedding-sdk-package.config.js b/rspack.embedding-sdk-package.config.js index 67dbacac28f..8d94033de81 100644 --- a/rspack.embedding-sdk-package.config.js +++ b/rspack.embedding-sdk-package.config.js @@ -1,6 +1,5 @@ /* eslint-env node */ /* eslint-disable import/no-commonjs */ -/* eslint-disable import/order */ const rspack = require("@rspack/core"); const mainConfig = require("./rspack.main.config"); diff --git a/rspack.main.config.js b/rspack.main.config.js index 37fa7fc8aff..8d15eba4d44 100644 --- a/rspack.main.config.js +++ b/rspack.main.config.js @@ -30,9 +30,7 @@ const { const { DropStylesEntryScriptPlugin, } = require("./frontend/build/shared/rspack/plugins/DropStylesEntryScriptPlugin"); -const { - RESOLVE_ALIASES, -} = require("./frontend/build/shared/rspack/resolve-aliases"); +const resolveConfig = require("./frontend/build/shared/rspack/resolve-config"); const { SIDE_EFFECT_FREE_RULE, } = require("./frontend/build/shared/rspack/side-effect-free-modules"); @@ -197,9 +195,7 @@ const config = { // we override it for dev mode below devtool: "source-map", - externals: { - canvg: "canvg", - }, + ...resolveConfig, // output to "dist" output: { @@ -286,16 +282,6 @@ const config = { }, ], }, - resolve: { - extensions: [".js", ".jsx", ".ts", ".tsx", ".css", ".svg"], - alias: RESOLVE_ALIASES, - fallback: { - buffer: require.resolve("buffer/"), - url: require.resolve("url/"), - events: require.resolve("events/"), - querystring: require.resolve("querystring-es3"), - }, - }, optimization: { runtimeChunk: "single", splitChunks: {