@scayle/eslint-config-storefront 4.8.2 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,220 @@
1
+ import comments from '@eslint-community/eslint-plugin-eslint-comments'
2
+ import pluginNode from 'eslint-plugin-n'
3
+ import jsonc from 'eslint-plugin-jsonc'
4
+ import pluginSonarjs from 'eslint-plugin-sonarjs'
5
+ import pluginPromise from 'eslint-plugin-promise'
6
+ import regexp from 'eslint-plugin-regexp'
7
+ import { importX as pluginImportX } from 'eslint-plugin-import-x'
8
+ import jsdoc from 'eslint-plugin-jsdoc'
9
+ import e18e from '@e18e/eslint-plugin'
10
+
11
+ // Small, single-plugin rule domains that don't warrant their own file. Each
12
+ // still exports its own named factory, so composition and imports elsewhere
13
+ // in the package are unaffected by living in this shared file.
14
+
15
+ // ESLint directive comment hygiene (e.g. eslint-disable) rules.
16
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries enforcing ESLint directive comment hygiene. */
17
+ export function commentsConfig() {
18
+ return [
19
+ {
20
+ name: 'storefront/comments/rules',
21
+ plugins: { 'eslint-comments': comments },
22
+ rules: {
23
+ 'eslint-comments/no-aggregating-enable': 'error',
24
+ 'eslint-comments/no-duplicate-disable': 'error',
25
+ 'eslint-comments/no-unlimited-disable': 'error',
26
+ 'eslint-comments/no-unused-enable': 'error',
27
+ },
28
+ },
29
+ ]
30
+ }
31
+
32
+ // Node.js best-practice rules.
33
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries for Node.js best-practice rules. */
34
+ export function nodeConfig() {
35
+ return [
36
+ {
37
+ name: 'storefront/node/rules',
38
+ plugins: { node: pluginNode },
39
+ rules: {
40
+ 'node/handle-callback-err': ['error', '^(err|error)$'],
41
+ 'node/no-deprecated-api': 'error',
42
+ 'node/no-exports-assign': 'error',
43
+ 'node/no-new-require': 'error',
44
+ 'node/no-path-concat': 'error',
45
+ 'node/prefer-global/buffer': ['error', 'never'],
46
+ 'node/prefer-global/process': ['error', 'never'],
47
+ 'node/process-exit-as-throw': 'error',
48
+ },
49
+ },
50
+ ]
51
+ }
52
+
53
+ // JSON / JSONC structural and stylistic rules.
54
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries for JSON/JSONC structural and stylistic rules. */
55
+ export function jsoncConfig() {
56
+ return [
57
+ ...jsonc.configs['flat/recommended-with-jsonc'],
58
+ {
59
+ name: 'storefront/jsonc/rules',
60
+ files: ['**/*.json', '**/*.json5', '**/*.jsonc'],
61
+ rules: {
62
+ 'jsonc/array-bracket-spacing': ['error', 'never'],
63
+ 'jsonc/comma-dangle': ['error', 'never'],
64
+ 'jsonc/comma-style': ['error', 'last'],
65
+ 'jsonc/indent': ['error', 2],
66
+ 'jsonc/key-spacing': [
67
+ 'error',
68
+ { afterColon: true, beforeColon: false },
69
+ ],
70
+ 'jsonc/object-curly-newline': [
71
+ 'error',
72
+ { consistent: true, multiline: true },
73
+ ],
74
+ 'jsonc/object-curly-spacing': ['error', 'always'],
75
+ 'jsonc/object-property-newline': [
76
+ 'error',
77
+ { allowAllPropertiesOnSameLine: true },
78
+ ],
79
+ 'jsonc/quote-props': 'error',
80
+ 'jsonc/quotes': 'error',
81
+ },
82
+ },
83
+ ]
84
+ }
85
+
86
+ // Pre-defined rule configurations for better performance
87
+ /** @type {import('eslint').Linter.RulesRecord} */
88
+ const SONARJS_RULES = {
89
+ 'sonarjs/no-duplicate-string': 'off',
90
+ 'sonarjs/todo-tag': 'warn',
91
+ 'sonarjs/fixme-tag': 'warn',
92
+ }
93
+
94
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries for SonarJS rules. */
95
+ export function sonarjsConfig() {
96
+ return [
97
+ /** @type {import('eslint').Linter.Config} */ (
98
+ pluginSonarjs.configs.recommended
99
+ ),
100
+ {
101
+ name: 'storefront/sonarjs/rules',
102
+ rules: SONARJS_RULES,
103
+ },
104
+ ]
105
+ }
106
+
107
+ // Pre-defined rule configurations for better performance
108
+ /** @type {import('eslint').Linter.RulesRecord} */
109
+ const PROMISE_RULES = {
110
+ 'promise/no-multiple-resolved': 'error',
111
+ 'promise/no-new-statics': 'error',
112
+ 'promise/no-return-wrap': 'error',
113
+ 'promise/always-return': 'warn',
114
+ 'promise/avoid-new': 'warn',
115
+ 'promise/catch-or-return': 'warn',
116
+ 'promise/no-callback-in-promise': 'warn',
117
+ 'promise/no-nesting': 'warn',
118
+ 'promise/no-promise-in-callback': 'warn',
119
+ 'promise/no-return-in-finally': 'warn',
120
+ 'promise/param-names': 'warn',
121
+ 'promise/valid-params': 'warn',
122
+ 'promise/no-native': 'off',
123
+ }
124
+
125
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries for Promise usage rules. */
126
+ export function promiseConfig() {
127
+ return [
128
+ {
129
+ name: 'storefront/promise/rules',
130
+ plugins: { promise: pluginPromise },
131
+ rules: PROMISE_RULES,
132
+ },
133
+ ]
134
+ }
135
+
136
+ // Regex correctness rules, including ReDoS-prone pattern detection.
137
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries for regex correctness rules. */
138
+ export function regexpConfig() {
139
+ return [
140
+ {
141
+ name: 'storefront/regexp/rules',
142
+ ...regexp.configs['flat/recommended'],
143
+ },
144
+ ]
145
+ }
146
+
147
+ // Pre-defined rule configurations for better performance
148
+ /** @type {import('eslint').Linter.RulesRecord} */
149
+ const IMPORT_X_RULES = {
150
+ 'import-x/consistent-type-specifier-style': ['error', 'prefer-top-level'],
151
+ 'import-x/first': 'error',
152
+ 'import-x/no-duplicates': 'error',
153
+ 'import-x/no-mutable-exports': 'error',
154
+ 'import-x/no-named-default': 'error',
155
+ 'import-x/no-self-import': 'error',
156
+ 'import-x/no-extraneous-dependencies': 'error',
157
+ }
158
+
159
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries for import/export correctness rules. */
160
+ export function importXConfig() {
161
+ return [
162
+ {
163
+ name: 'storefront/import-x/rules',
164
+ plugins: { 'import-x': pluginImportX },
165
+ rules: IMPORT_X_RULES,
166
+ },
167
+ ]
168
+ }
169
+
170
+ // JSDoc validation. Storefront SDK packages require JSDoc on every exported symbol.
171
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries validating JSDoc comments. */
172
+ export function jsdocConfig() {
173
+ return [
174
+ {
175
+ name: 'storefront/jsdoc/rules',
176
+ plugins: { jsdoc },
177
+ rules: {
178
+ 'jsdoc/check-access': 'warn',
179
+ 'jsdoc/check-param-names': 'warn',
180
+ 'jsdoc/check-property-names': 'warn',
181
+ 'jsdoc/check-types': 'warn',
182
+ 'jsdoc/empty-tags': 'warn',
183
+ 'jsdoc/implements-on-classes': 'warn',
184
+ 'jsdoc/no-defaults': 'warn',
185
+ 'jsdoc/no-multi-asterisks': 'warn',
186
+ 'jsdoc/require-param-name': 'warn',
187
+ 'jsdoc/require-property': 'warn',
188
+ 'jsdoc/require-property-description': 'warn',
189
+ 'jsdoc/require-property-name': 'warn',
190
+ 'jsdoc/require-returns-check': 'warn',
191
+ 'jsdoc/require-returns-description': 'warn',
192
+ 'jsdoc/require-yields-check': 'warn',
193
+ 'jsdoc/check-alignment': 'warn',
194
+ 'jsdoc/multiline-blocks': 'warn',
195
+ },
196
+ },
197
+ ]
198
+ }
199
+
200
+ // Modernization, module-replacement, and performance rules from the e18e
201
+ // community (https://e18e.dev). Uses the plugin's own `recommended` preset
202
+ // (all three of its rule categories), downgraded to `warn`: the plugin is
203
+ // young (pre-1.0) and its rule set hasn't been reviewed against this
204
+ // codebase yet, so findings surface without blocking CI.
205
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries for e18e modernization and performance rules. */
206
+ export function e18eConfig() {
207
+ const recommendedRules = e18e.configs.recommended.rules ?? {}
208
+ /** @type {import('eslint').Linter.RulesRecord} */
209
+ const rules = Object.fromEntries(
210
+ Object.keys(recommendedRules).map((ruleName) => [ruleName, 'warn']),
211
+ )
212
+
213
+ return [
214
+ {
215
+ name: 'storefront/e18e/rules',
216
+ plugins: e18e.configs.recommended.plugins,
217
+ rules,
218
+ },
219
+ ]
220
+ }
@@ -0,0 +1,36 @@
1
+ import vitest from '@vitest/eslint-plugin'
2
+ import noOnlyTests from 'eslint-plugin-no-only-tests'
3
+
4
+ export const GLOB_TESTS = [
5
+ '**/__tests__/**/*.{js,jsx,ts,tsx,mjs,cjs,mjsx,cts,ctsx}',
6
+ '**/*.spec.*',
7
+ '**/*.test.*',
8
+ '**/*.bench.*',
9
+ '**/*.benchmark.*',
10
+ ]
11
+
12
+ // Test-file rules (Vitest conventions, no `.only` left behind).
13
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries for test-file conventions. */
14
+ export function testConfig() {
15
+ return [
16
+ {
17
+ name: 'storefront/test/rules',
18
+ files: GLOB_TESTS,
19
+ plugins: {
20
+ vitest,
21
+ 'no-only-tests': noOnlyTests,
22
+ },
23
+ rules: {
24
+ 'vitest/consistent-test-it': [
25
+ 'error',
26
+ { fn: 'it', withinDescribe: 'it' },
27
+ ],
28
+ 'vitest/no-identical-title': 'error',
29
+ 'vitest/no-import-node-test': 'error',
30
+ 'no-only-tests/no-only-tests': process.env.CI ? 'error' : 'warn',
31
+ 'vitest/prefer-hooks-in-order': 'error',
32
+ 'vitest/prefer-lowercase-title': 'error',
33
+ },
34
+ },
35
+ ]
36
+ }
@@ -0,0 +1,92 @@
1
+ import { defineConfig } from 'eslint/config'
2
+ import tseslint from 'typescript-eslint'
3
+
4
+ // TypeScript rules. Type-aware rules only activate when `tsconfigPath` is provided,
5
+ // since they require a full TypeScript program build (`parserOptions.projectService`).
6
+ //
7
+ // The `@typescript-eslint` plugin is registered unscoped (no `files`) so that consumers
8
+ // can reference `@typescript-eslint/*` rules from their own `files: ['**/*.vue']`
9
+ // overrides (the plugin, not just the rule set, must be visible to those file globs too).
10
+ // Core `no-unused-vars` is turned off only for the TypeScript file types below, scoped
11
+ // to the same `files:` array as `@typescript-eslint/no-unused-vars`: it isn't type-aware,
12
+ // so it misreads TypeScript-only constructs (e.g. a named parameter inside a type literal)
13
+ // as real unused bindings. `.js`/`.mjs`/`.cjs` files keep the core rule from javascript.mjs.
14
+ // Consumers that wire up `@typescript-eslint/parser` for another extension (like `.vue` via
15
+ // `vue-eslint-parser`) should register `@typescript-eslint/no-unused-vars` there themselves,
16
+ // since only they know which file types actually parse as TypeScript.
17
+ /**
18
+ * @param {object} [config]
19
+ * @param {string} [config.tsconfigPath] Absolute path to the tsconfig.json to type-check
20
+ * against. When omitted, only non-type-aware TypeScript rules apply.
21
+ * @returns {import('eslint').Linter.Config[]} Flat config entries for TypeScript rules.
22
+ */
23
+ export function typescriptConfig(config = {}) {
24
+ const { tsconfigPath } = config
25
+
26
+ return defineConfig(
27
+ {
28
+ name: 'storefront/typescript/plugin',
29
+ plugins: { '@typescript-eslint': tseslint.plugin },
30
+ },
31
+ {
32
+ name: 'storefront/typescript',
33
+ files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
34
+ extends: [
35
+ tseslint.configs.recommended,
36
+ ...(tsconfigPath ? [tseslint.configs.recommendedTypeChecked] : []),
37
+ ],
38
+ ...(tsconfigPath
39
+ ? {
40
+ languageOptions: {
41
+ parserOptions: {
42
+ projectService: { defaultProject: tsconfigPath },
43
+ tsconfigRootDir: process.cwd(),
44
+ },
45
+ },
46
+ }
47
+ : {}),
48
+ rules: {
49
+ 'no-unused-vars': 'off',
50
+ 'no-use-before-define': 'off',
51
+ '@typescript-eslint/no-use-before-define': [
52
+ 'error',
53
+ { functions: false, classes: false, variables: true },
54
+ ],
55
+ '@typescript-eslint/consistent-type-definitions': [
56
+ 'error',
57
+ 'interface',
58
+ ],
59
+ '@typescript-eslint/consistent-type-imports': [
60
+ 'error',
61
+ {
62
+ prefer: 'type-imports',
63
+ fixStyle: 'separate-type-imports',
64
+ disallowTypeAnnotations: false,
65
+ },
66
+ ],
67
+ '@typescript-eslint/method-signature-style': ['error', 'property'],
68
+ '@typescript-eslint/no-import-type-side-effects': 'error',
69
+ '@typescript-eslint/no-explicit-any': [
70
+ 'warn',
71
+ { ignoreRestArgs: true },
72
+ ],
73
+ '@typescript-eslint/no-empty-object-type': [
74
+ 'error',
75
+ { allowInterfaces: 'always' },
76
+ ],
77
+ '@typescript-eslint/no-unused-vars': [
78
+ 'error',
79
+ {
80
+ args: 'all',
81
+ argsIgnorePattern: '^_',
82
+ caughtErrors: 'all',
83
+ caughtErrorsIgnorePattern: '^_',
84
+ destructuredArrayIgnorePattern: '^_',
85
+ varsIgnorePattern: '^_',
86
+ ignoreRestSiblings: true,
87
+ },
88
+ ],
89
+ },
90
+ },
91
+ )
92
+ }
@@ -0,0 +1,33 @@
1
+ import pluginUnicorn from 'eslint-plugin-unicorn'
2
+
3
+ const GLOB_SRC = '**/*.?([cm])[jt]s?(x)'
4
+
5
+ // Modernization rules: a curated subset of eslint-plugin-unicorn, not its full
6
+ // `recommended` preset (hundreds of opinionated rules, many stylistic).
7
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries for modernization rules. */
8
+ export function unicornConfig() {
9
+ return [
10
+ {
11
+ name: 'storefront/unicorn/rules',
12
+ files: [GLOB_SRC],
13
+ plugins: { unicorn: pluginUnicorn },
14
+ rules: {
15
+ 'unicorn/consistent-empty-array-spread': 'error',
16
+ 'unicorn/error-message': 'error',
17
+ 'unicorn/escape-case': 'error',
18
+ 'unicorn/new-for-builtins': 'error',
19
+ 'unicorn/no-instanceof-builtins': 'error',
20
+ 'unicorn/no-new-array': 'error',
21
+ 'unicorn/no-new-buffer': 'error',
22
+ 'unicorn/number-literal-case': 'error',
23
+ 'unicorn/prefer-dom-node-text-content': 'error',
24
+ 'unicorn/prefer-includes': 'error',
25
+ 'unicorn/prefer-node-protocol': 'error',
26
+ 'unicorn/prefer-number-properties': 'error',
27
+ 'unicorn/prefer-string-starts-ends-with': 'error',
28
+ 'unicorn/prefer-type-error': 'error',
29
+ 'unicorn/throw-new-error': 'error',
30
+ },
31
+ },
32
+ ]
33
+ }
@@ -0,0 +1,10 @@
1
+ import storybook from 'eslint-plugin-storybook'
2
+
3
+ // https://github.com/storybookjs/eslint-plugin-storybook
4
+ // Applies only to files matching `*.stories.*` / `*.story.*`.
5
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries for Storybook file conventions. */
6
+ export function storybookConfig() {
7
+ return /** @type {import('eslint').Linter.Config[]} */ (
8
+ storybook.configs['flat/recommended']
9
+ )
10
+ }
@@ -0,0 +1,30 @@
1
+ import { defineConfig } from 'eslint/config'
2
+ import eslintPluginBetterTailwindcss from 'eslint-plugin-better-tailwindcss'
3
+
4
+ /**
5
+ * @param {object} config
6
+ * @param {string} config.entryPoint Path to the project's Tailwind CSS entry point
7
+ * (e.g. `./src/client/index.css`), passed to the plugin's `entryPoint` setting.
8
+ * @returns {import('eslint').Linter.Config[]} Flat config entries for Tailwind CSS class validation.
9
+ */
10
+ export function tailwindConfig(config) {
11
+ const { entryPoint } = config ?? {}
12
+
13
+ if (!entryPoint) {
14
+ throw new Error('storefrontV3: config.tailwind.entryPoint is required')
15
+ }
16
+
17
+ return defineConfig({
18
+ name: 'storefront/v3/tailwind',
19
+ extends: [eslintPluginBetterTailwindcss.configs.recommended],
20
+ rules: {
21
+ 'better-tailwindcss/enforce-consistent-line-wrapping': 'off',
22
+ // The eslint plugin uses a different class order than the prettier plugin.
23
+ // Disabled to avoid conflicts between the two.
24
+ 'better-tailwindcss/enforce-consistent-class-order': 'off',
25
+ },
26
+ settings: {
27
+ 'better-tailwindcss': { entryPoint },
28
+ },
29
+ })
30
+ }
@@ -0,0 +1,153 @@
1
+ import tseslint from 'typescript-eslint'
2
+ import vueParser from 'vue-eslint-parser'
3
+ import pluginVue from 'eslint-plugin-vue'
4
+ import pluginVueA11y from 'eslint-plugin-vuejs-accessibility'
5
+
6
+ const GLOB_VUE = ['**/*.vue']
7
+
8
+ // Vue parser wiring so `.vue` files (including `<script setup lang="ts">`) parse correctly.
9
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries wiring the Vue parser. */
10
+ function vueLanguageConfig() {
11
+ return [
12
+ ...pluginVue.configs['flat/essential'],
13
+ ...pluginVue.configs['flat/strongly-recommended'],
14
+ ...pluginVue.configs['flat/recommended'],
15
+ {
16
+ name: 'storefront/v3/vue-language',
17
+ files: GLOB_VUE,
18
+ languageOptions: {
19
+ parser: vueParser,
20
+ parserOptions: {
21
+ ecmaVersion: 'latest',
22
+ parser: tseslint.parser,
23
+ sourceType: 'module',
24
+ extraFileExtensions: ['.vue'],
25
+ ecmaFeatures: { jsx: true },
26
+ },
27
+ },
28
+ },
29
+ ]
30
+ }
31
+
32
+ /**
33
+ * @param {object} [config]
34
+ * @param {string[]} [config.undefComponentIgnores] Custom element tag names (e.g. web
35
+ * components) to exempt from `vue/no-undef-components`.
36
+ * @returns {import('eslint').Linter.Config[]} Flat config entries for Vue-specific rule overrides.
37
+ */
38
+ function vueRulesConfig(config = {}) {
39
+ const { undefComponentIgnores = [] } = config
40
+
41
+ return [
42
+ {
43
+ name: 'storefront/v3/vue-rules',
44
+ files: GLOB_VUE,
45
+ plugins: { vue: pluginVue },
46
+ rules: {
47
+ 'eslint-comments/no-unlimited-disable': 'off',
48
+ // Core no-unused-vars isn't type-aware and misreads TypeScript-only
49
+ // constructs in <script setup lang="ts"> as real unused bindings.
50
+ // typescript.mjs only scopes the @typescript-eslint replacement to
51
+ // .ts/.tsx/.mts/.cts, so .vue files need their own registration.
52
+ 'no-unused-vars': 'off',
53
+ '@typescript-eslint/no-unused-vars': [
54
+ 'error',
55
+ {
56
+ args: 'all',
57
+ argsIgnorePattern: '^_',
58
+ caughtErrors: 'all',
59
+ caughtErrorsIgnorePattern: '^_',
60
+ destructuredArrayIgnorePattern: '^_',
61
+ varsIgnorePattern: '^_',
62
+ ignoreRestSiblings: true,
63
+ },
64
+ ],
65
+ '@typescript-eslint/no-empty-object-type': [
66
+ 'error',
67
+ { allowInterfaces: 'always' },
68
+ ],
69
+ 'vue/html-self-closing': [
70
+ 'error',
71
+ {
72
+ html: { void: 'any', normal: 'any', component: 'always' },
73
+ },
74
+ ],
75
+ 'vue/v-bind-style': [
76
+ 'error',
77
+ 'shorthand',
78
+ { sameNameShorthand: 'never' },
79
+ ],
80
+ 'vue/no-undef-components': [
81
+ 'error',
82
+ { ignorePatterns: undefComponentIgnores },
83
+ ],
84
+ 'vue/define-emits-declaration': ['error', 'type-literal'],
85
+ 'vue/custom-event-name-casing': ['error', 'camelCase'],
86
+ 'vue/slot-name-casing': ['error', 'kebab-case'],
87
+ 'vue/require-default-prop': 'off',
88
+ 'vue/multi-word-component-names': 'warn',
89
+ 'vue/block-order': [
90
+ 'error',
91
+ { order: ['template', 'script', 'style'] },
92
+ ],
93
+ // Vue Styling
94
+ // Based on https://github.com/nuxt/eslint/blob/main/packages/eslint-config/src/configs/vue.ts#L142
95
+ 'vue/html-closing-bracket-newline': 'off',
96
+ 'vue/html-closing-bracket-spacing': 'off',
97
+ 'vue/html-indent': 'off',
98
+ 'vue/html-quotes': 'off',
99
+ 'vue/max-attributes-per-line': 'off',
100
+ 'vue/multiline-html-element-content-newline': 'off',
101
+ 'vue/mustache-interpolation-spacing': 'off',
102
+ 'vue/no-multi-spaces': 'off',
103
+ 'vue/no-spaces-around-equal-signs-in-attribute': 'off',
104
+ 'vue/singleline-html-element-content-newline': 'off',
105
+ },
106
+ },
107
+ ]
108
+ }
109
+
110
+ // Vue accessibility. Every rule ships `off` by default: this preset's baseline reflects
111
+ // that a11y coverage is opt-in until a consumer's own audit turns rules on deliberately,
112
+ // not a statement that these rules don't matter.
113
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries for Vue accessibility rules, defaulted off. */
114
+ function vueA11yConfig() {
115
+ return [
116
+ ...pluginVueA11y.configs['flat/recommended'],
117
+ {
118
+ name: 'storefront/v3/vue-a11y',
119
+ files: GLOB_VUE,
120
+ rules: {
121
+ 'vuejs-accessibility/mouse-events-have-key-events': 'off',
122
+ 'vuejs-accessibility/no-static-element-interactions': 'off',
123
+ 'vuejs-accessibility/anchor-has-content': 'off',
124
+ 'vuejs-accessibility/aria-props': 'off',
125
+ 'vuejs-accessibility/aria-role': 'off',
126
+ 'vuejs-accessibility/aria-unsupported-elements': 'off',
127
+ 'vuejs-accessibility/click-events-have-key-events': 'off',
128
+ 'vuejs-accessibility/form-control-has-label': 'off',
129
+ 'vuejs-accessibility/heading-has-content': 'off',
130
+ 'vuejs-accessibility/iframe-has-title': 'off',
131
+ 'vuejs-accessibility/interactive-supports-focus': 'off',
132
+ 'vuejs-accessibility/label-has-for': 'off',
133
+ 'vuejs-accessibility/media-has-caption': 'off',
134
+ 'vuejs-accessibility/no-access-key': 'off',
135
+ 'vuejs-accessibility/no-autofocus': 'off',
136
+ 'vuejs-accessibility/no-distracting-elements': 'off',
137
+ 'vuejs-accessibility/no-redundant-roles': 'off',
138
+ 'vuejs-accessibility/role-has-required-aria-props': 'off',
139
+ 'vuejs-accessibility/tabindex-no-positive': 'off',
140
+ },
141
+ },
142
+ ]
143
+ }
144
+
145
+ /**
146
+ * @param {object} [config]
147
+ * @param {string[]} [config.undefComponentIgnores] Custom element tag names exempt from
148
+ * `vue/no-undef-components`.
149
+ * @returns {import('eslint').Linter.Config[]} Flat config entries for Vue language wiring, rules, and accessibility.
150
+ */
151
+ export function vueConfig(config = {}) {
152
+ return [...vueLanguageConfig(), ...vueRulesConfig(config), ...vueA11yConfig()]
153
+ }
@@ -0,0 +1,29 @@
1
+ import vueComposable from '@scayle/eslint-plugin-vue-composable'
2
+
3
+ /**
4
+ * @param {object} [config]
5
+ * @param {string[]} [config.fallthroughAttrsIgnores] Path fragments (e.g. `/src/client/pages/`)
6
+ * exempt from `require-explicit-fallthrough-attrs`, for components that never receive
7
+ * fallthrough attributes (page/layout roots mounted by a router).
8
+ * @returns {import('eslint').Linter.Config[]} Flat config entries for `@scayle/vue-composable` rules.
9
+ */
10
+ export function vueComposableConfig(config = {}) {
11
+ const { fallthroughAttrsIgnores = [] } = config
12
+
13
+ return [
14
+ {
15
+ name: 'storefront/v3/vue-composable',
16
+ plugins: { '@scayle/vue-composable': vueComposable },
17
+ rules: {
18
+ '@scayle/vue-composable/no-composable-after-await': 'error',
19
+ '@scayle/vue-composable/no-lifecycle-after-await': 'error',
20
+ '@scayle/vue-composable/no-watch-after-await': 'error',
21
+ '@scayle/vue-composable/no-computed-after-await': 'error',
22
+ '@scayle/vue-composable/require-explicit-fallthrough-attrs': [
23
+ 'error',
24
+ { ignores: fallthroughAttrsIgnores },
25
+ ],
26
+ },
27
+ },
28
+ ]
29
+ }
@@ -0,0 +1,52 @@
1
+ import vueI18n from '@intlify/eslint-plugin-vue-i18n'
2
+
3
+ /**
4
+ * @param {object} config
5
+ * @param {string} config.localeDir Glob pointing at the project's locale JSON files
6
+ * (e.g. `./src/i18n/locales/*.json`), passed to the `vue-i18n` plugin's `localeDir` setting.
7
+ * @param {string} [config.messageSyntaxVersion] `vue-i18n` message syntax version.
8
+ * @param {string[]} [config.unusedKeyIgnores] Regex-string patterns (e.g. `/foo.bar/`) for keys
9
+ * used dynamically in code, which `no-unused-keys` can't detect statically.
10
+ * @returns {import('eslint').Linter.Config[]} Flat config entries for `vue-i18n` linting.
11
+ */
12
+ export function vueI18nConfig(config) {
13
+ const {
14
+ localeDir,
15
+ messageSyntaxVersion = '^11.2.8',
16
+ unusedKeyIgnores = [],
17
+ } = config ?? {}
18
+
19
+ if (!localeDir) {
20
+ throw new Error('storefrontV3: config.i18n.localeDir is required')
21
+ }
22
+
23
+ return [
24
+ ...vueI18n.configs['flat/base'],
25
+ {
26
+ name: 'storefront/v3/vue-i18n',
27
+ rules: {
28
+ '@intlify/vue-i18n/valid-message-syntax': 'error',
29
+ // Passes when any of the locale files contains a used key
30
+ '@intlify/vue-i18n/no-missing-keys': 'error',
31
+ // Passes when all locale files contain the same keys.
32
+ '@intlify/vue-i18n/no-missing-keys-in-other-locales': process.env.CI
33
+ ? 'error'
34
+ : 'warn',
35
+ '@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error',
36
+ '@intlify/vue-i18n/no-dynamic-keys': 'off',
37
+ // Performance-intensive: scans all matching files for unused translation keys.
38
+ '@intlify/vue-i18n/no-unused-keys': [
39
+ 'error',
40
+ {
41
+ src: './src/{client,server,shared}/**',
42
+ extensions: ['.vue', '.ts'],
43
+ ignores: unusedKeyIgnores,
44
+ },
45
+ ],
46
+ },
47
+ settings: {
48
+ 'vue-i18n': { localeDir, messageSyntaxVersion },
49
+ },
50
+ },
51
+ ]
52
+ }