import { createConfigForNuxt } from '@nuxt/eslint-config/flat'; import pluginVueA11y from 'eslint-plugin-vuejs-accessibility'; export default createConfigForNuxt({ plugins: { 'vuejs-accessibility': pluginVueA11y, }, rules: { 'vue/html-self-closing': [ 'off', { html: { void: 'any', normal: 'any', component: 'any', }, }, ], 'vue/v-on-event-hyphenation': 'off', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/ban-ts-comment': 'off', 'no-unused-vars': [ 'error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true, argsIgnorePattern: '^_', varsIgnorePattern: '^_', }, ], '@typescript-eslint/no-unused-vars': [ 'error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true, argsIgnorePattern: '^_', varsIgnorePattern: '^_', }, ], '@typescript-eslint/consistent-type-imports': [ 'error', { prefer: 'type-imports', fixStyle: 'separate-type-imports', }, ], // Prevent unsafe array access patterns that can cause SSR crashes // This rule catches patterns like: obj?.prop.length (without optional chaining on .length) 'no-unsafe-optional-chaining': [ 'error', { disallowArithmeticOperators: true, }, ], // Accessibility rules 'vuejs-accessibility/alt-text': [ 'error', { elements: ['img', 'object', 'area', 'input[type="image"]'], img: ['Image'], object: ['Object'], area: ['Area'], 'input[type="image"]': ['ImageInput'], }, ], 'vuejs-accessibility/anchor-has-content': 'error', 'vuejs-accessibility/aria-props': 'error', 'vuejs-accessibility/aria-role': [ 'error', { ignoreNonDOM: true, }, ], 'vuejs-accessibility/aria-unsupported-elements': 'error', 'vuejs-accessibility/click-events-have-key-events': 'error', 'vuejs-accessibility/form-has-label': 'error', 'vuejs-accessibility/heading-has-content': 'error', 'vuejs-accessibility/label-has-for': [ 'error', { required: { some: ['nesting', 'id'], }, }, ], 'vuejs-accessibility/media-has-caption': 'error', 'vuejs-accessibility/mouse-events-have-key-events': 'error', 'vuejs-accessibility/no-autofocus': 'error', 'vuejs-accessibility/no-onchange': 'error', 'vuejs-accessibility/no-redundant-roles': 'error', 'vuejs-accessibility/no-access-key': 'error', 'vuejs-accessibility/no-distracting-elements': 'error', 'vuejs-accessibility/no-static-element-interactions': 'error', 'vuejs-accessibility/tabindex-no-positive': 'error', }, languageOptions: { parserOptions: { project: true, }, }, }).override('nuxt/typescript/rules', { rules: { 'vue/html-self-closing': 0, '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/ban-ts-comment': 'off', // Add the same rules here to ensure they're not overridden '@typescript-eslint/no-unused-vars': [ 'error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true, argsIgnorePattern: '^_', varsIgnorePattern: '^_', }, ], '@typescript-eslint/consistent-type-imports': [ 'error', { prefer: 'type-imports', fixStyle: 'separate-type-imports', }, ], // Prevent unsafe array access patterns that can cause SSR crashes // This rule catches patterns like: obj?.prop.length (without optional chaining on .length) 'no-unsafe-optional-chaining': [ 'error', { disallowArithmeticOperators: true, }, ], }, }).append({ // cloud_functions/ holds deployed GCP function source. It is kept in the repo // (tracked, not gitignored) so a fresh deployment has what it needs, but it is // not part of the app's lint surface. The Nuxt flat config otherwise picks it // up via .gitignore, so ignore it explicitly here. ignores: ['cloud_functions/**'], }).append({ // Test specs cast Apollo/mocks with `as any` by design (e.g. // `(useMutation as any).mock`). Keep no-explicit-any relaxed in specs while it // is enforced as an error in application source. Genuine external-lib interop // in source (e.g. Three.js in StlViewer.vue) uses targeted inline disables. files: ['**/*.spec.ts', '**/*.test.ts'], rules: { '@typescript-eslint/no-explicit-any': 'off', }, }).append({ // Stylistic Vue rules we intentionally do not enforce. The Nuxt Vue preset // re-enables vue/html-self-closing after the top-level override, so this final // append is what actually silences it (and vue/first-attribute-linebreak). rules: { 'vue/html-self-closing': 'off', 'vue/first-attribute-linebreak': 'off', }, });