import js from '@eslint/js' import globals from 'globals' import tseslint from 'typescript-eslint' /** * ESLint configuration for the runtime SDK. * * The SDK is written in JavaScript with JSDoc types rather than TypeScript, * because it is served as source to be read as well as to be executed. That * choice drives the rule set: * * - The rules designed for a `.ts` boundary (`explicit-module-boundary-types`) * are redundant here: a JSDoc `@returns` annotation is the same information, * and the linter cannot see it as an annotation. * - The rules that catch real defects — floating promises, misused promises, * unreachable switch cases, unused variables — stay, and are errors. * - `no-console` is an error rather than a warning, because this module runs in * someone else's page and must not write to their console on a path they did * not ask for. The two exceptions below are deliberate diagnostics that only * fire when the theme itself has already failed. * * The SDK is loaded by third-party pages, so anything it gets wrong is somebody * else's incident. The strictness here is proportionate to that. */ export default tseslint.config( { ignores: ['dist', 'node_modules', '*.config.js'] }, js.configs.recommended, ...tseslint.configs.recommendedTypeChecked, ...tseslint.configs.stylisticTypeChecked, { languageOptions: { parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname }, globals: { ...globals.browser, ...globals.es2022 }, }, plugins: { '@typescript-eslint': tseslint.plugin }, rules: { // JSDoc already declares every boundary type in a JavaScript project. '@typescript-eslint/explicit-module-boundary-types': 'off', // A fetch stub that returns a Response directly has nothing to await. '@typescript-eslint/require-await': 'off', // `||` is deliberate where an empty string should fall through, which is // not what `??` does. '@typescript-eslint/prefer-nullish-coalescing': 'off', // --- correctness ----------------------------------------------------- '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-misused-promises': 'error', '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/switch-exhaustiveness-check': 'error', // Disabled in this package. The DOM type definitions describe a fully // featured modern browser, but the runtime guards below exist for Safari // below 14 (no `MediaQueryList.addEventListener`) and for embedded // webviews with no `matchMedia` at all. Those guards are load-bearing for // the consumers this package exists to serve, and the rule reports them as // dead code. '@typescript-eslint/no-unnecessary-condition': 'off', '@typescript-eslint/no-unnecessary-type-assertion': 'error', '@typescript-eslint/consistent-type-imports': [ 'error', { prefer: 'type-imports', fixStyle: 'inline-type-imports' }, ], '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrors: 'none' }, ], // --- host safety ----------------------------------------------------- 'no-console': ['error', { allow: ['warn', 'error'] }], eqeqeq: ['error', 'always', { null: 'ignore' }], 'prefer-const': 'error', 'no-var': 'error', 'object-shorthand': 'error', // An unsubscribe function that does nothing is the correct return value // when there is nothing to unsubscribe from. '@typescript-eslint/no-empty-function': [ 'error', { allow: ['arrowFunctions'] }, ], }, }, { // Tests build doubles that are deliberately loosely typed, and a test's // console output is how a failure is diagnosed. files: ['**/*.test.js'], rules: { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-call': 'off', '@typescript-eslint/no-unsafe-return': 'off', '@typescript-eslint/unbound-method': 'off', 'no-console': 'off', }, }, )