go-bananas/sdk/eslint.config.js

95 lines
4.2 KiB
JavaScript
Raw Normal View History

2026-10-07 15:11:35 +02:00
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',
},
},
)