@scayle/eslint-config-storefront 4.8.3-alpha.1 → 5.1.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,159 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+
4
+ // Core JavaScript correctness rules, plus a handful of Storefront-specific overrides
5
+ // on top of `@eslint/js`'s `recommended` preset.
6
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries for core JavaScript correctness rules. */
7
+ export function javascriptConfig() {
8
+ return [
9
+ {
10
+ name: 'storefront/javascript/setup',
11
+ languageOptions: {
12
+ ecmaVersion: 'latest',
13
+ sourceType: 'module',
14
+ globals: {
15
+ ...globals.browser,
16
+ ...globals.es2021,
17
+ ...globals.node,
18
+ },
19
+ parserOptions: {
20
+ ecmaFeatures: { jsx: true },
21
+ },
22
+ },
23
+ },
24
+ {
25
+ name: 'storefront/javascript/rules',
26
+ rules: {
27
+ ...js.configs.recommended.rules,
28
+ 'accessor-pairs': [
29
+ 'error',
30
+ { enforceForClassMembers: true, setWithoutGet: true },
31
+ ],
32
+ 'array-callback-return': 'error',
33
+ 'block-scoped-var': 'error',
34
+ 'default-case-last': 'error',
35
+ 'dot-notation': ['error', { allowKeywords: true }],
36
+ eqeqeq: ['error', 'smart'],
37
+ 'new-cap': [
38
+ 'error',
39
+ { capIsNew: false, newIsCap: true, properties: true },
40
+ ],
41
+ 'no-alert': 'error',
42
+ 'no-caller': 'error',
43
+ 'no-eval': 'error',
44
+ 'no-extend-native': 'error',
45
+ 'no-extra-bind': 'error',
46
+ 'no-implied-eval': 'error',
47
+ 'no-iterator': 'error',
48
+ 'no-labels': ['error', { allowLoop: false, allowSwitch: false }],
49
+ 'no-lone-blocks': 'error',
50
+ 'no-multi-str': 'error',
51
+ 'no-new': 'error',
52
+ 'no-new-func': 'error',
53
+ 'no-new-wrappers': 'error',
54
+ 'no-octal-escape': 'error',
55
+ 'no-proto': 'error',
56
+ 'no-restricted-globals': [
57
+ 'error',
58
+ { name: 'global', message: 'Use `globalThis` instead.' },
59
+ { name: 'self', message: 'Use `globalThis` instead.' },
60
+ ],
61
+ 'no-restricted-properties': [
62
+ 'error',
63
+ {
64
+ property: '__proto__',
65
+ message:
66
+ 'Use `Object.getPrototypeOf` or `Object.setPrototypeOf` instead.',
67
+ },
68
+ {
69
+ property: '__defineGetter__',
70
+ message: 'Use `Object.defineProperty` instead.',
71
+ },
72
+ {
73
+ property: '__defineSetter__',
74
+ message: 'Use `Object.defineProperty` instead.',
75
+ },
76
+ {
77
+ property: '__lookupGetter__',
78
+ message: 'Use `Object.getOwnPropertyDescriptor` instead.',
79
+ },
80
+ {
81
+ property: '__lookupSetter__',
82
+ message: 'Use `Object.getOwnPropertyDescriptor` instead.',
83
+ },
84
+ ],
85
+ 'no-self-compare': 'error',
86
+ 'no-sequences': 'error',
87
+ 'no-template-curly-in-string': 'error',
88
+ 'no-throw-literal': 'error',
89
+ 'no-undef-init': 'error',
90
+ 'no-unmodified-loop-condition': 'error',
91
+ 'no-unneeded-ternary': ['error', { defaultAssignment: false }],
92
+ 'no-unreachable-loop': 'error',
93
+ 'no-unused-expressions': [
94
+ 'error',
95
+ {
96
+ allowShortCircuit: true,
97
+ allowTernary: true,
98
+ allowTaggedTemplates: true,
99
+ },
100
+ ],
101
+ 'no-unused-vars': [
102
+ 'error',
103
+ {
104
+ args: 'all',
105
+ argsIgnorePattern: '^_',
106
+ caughtErrors: 'all',
107
+ caughtErrorsIgnorePattern: '^_',
108
+ destructuredArrayIgnorePattern: '^_',
109
+ varsIgnorePattern: '^_',
110
+ ignoreRestSiblings: true,
111
+ },
112
+ ],
113
+ 'no-use-before-define': [
114
+ 'error',
115
+ { functions: false, classes: false, variables: true },
116
+ ],
117
+ 'no-useless-call': 'error',
118
+ 'no-useless-computed-key': 'error',
119
+ // Off: a subclass constructor that only forwards to `super(...)` is
120
+ // still meaningful documentation of the accepted argument types at
121
+ // an extension point (e.g. a mapper/service subclass), even when it
122
+ // matches the base class's signature exactly.
123
+ 'no-useless-constructor': 'off',
124
+ 'no-useless-rename': 'error',
125
+ 'no-useless-return': 'error',
126
+ 'no-var': 'error',
127
+ 'object-shorthand': [
128
+ 'error',
129
+ 'always',
130
+ { avoidQuotes: true, ignoreConstructors: false },
131
+ ],
132
+ 'one-var': ['error', { initialized: 'never' }],
133
+ 'prefer-arrow-callback': [
134
+ 'error',
135
+ { allowNamedFunctions: false, allowUnboundThis: true },
136
+ ],
137
+ 'prefer-const': [
138
+ 'error',
139
+ { destructuring: 'all', ignoreReadBeforeAssign: true },
140
+ ],
141
+ 'prefer-exponentiation-operator': 'error',
142
+ 'prefer-promise-reject-errors': 'error',
143
+ 'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }],
144
+ 'prefer-rest-params': 'error',
145
+ 'prefer-spread': 'error',
146
+ 'prefer-template': 'error',
147
+ 'symbol-description': 'error',
148
+ 'unicode-bom': ['error', 'never'],
149
+ 'use-isnan': [
150
+ 'error',
151
+ { enforceForIndexOf: true, enforceForSwitchCase: true },
152
+ ],
153
+ 'valid-typeof': ['error', { requireStringLiterals: true }],
154
+ 'vars-on-top': 'error',
155
+ yoda: ['error', 'never'],
156
+ },
157
+ },
158
+ ]
159
+ }
@@ -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,71 @@
1
+ import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript'
2
+
3
+ // Resolves aliased imports (e.g. tsconfig.json `paths` like `@client/*`) to their real
4
+ // files, so import-x/no-restricted-paths, import-x/no-cycle, and import-x/order can see
5
+ // through them instead of only matching literal relative imports. Without this, every
6
+ // rule that compares resolved paths silently no-ops on an alias it can't resolve.
7
+ /**
8
+ * @param {string} [tsconfigPath] Absolute path to the tsconfig.json to resolve aliases
9
+ * against (from `resolver.tsconfigPath`, falling back to `typescript.tsconfigPath`).
10
+ * Omit both to use the resolver's own `<cwd>/tsconfig.json` default.
11
+ * @returns {import('eslint').Linter.Config[]} Flat config entries wiring the resolver and
12
+ * the setting import-x/no-cycle needs to use it correctly.
13
+ */
14
+ export function importResolverConfig(tsconfigPath) {
15
+ return [
16
+ {
17
+ name: 'storefront/v3/import-resolver',
18
+ settings: {
19
+ 'import-x/resolver-next': [
20
+ createTypeScriptImportResolver(
21
+ tsconfigPath ? { project: tsconfigPath } : undefined,
22
+ ),
23
+ ],
24
+ // import-x/no-cycle builds its own export map per file and gates that on
25
+ // this setting (distinct from the import-x/extensions *rule*). It defaults
26
+ // to .js/.mjs/.cjs only, so without this, every .ts/.tsx file silently
27
+ // returns a null export map and cycles through them go undetected past
28
+ // depth 1, alias or not.
29
+ //
30
+ // .vue is deliberately not listed here: the export map builder's parser
31
+ // (via import-x/parsers) can't replicate the <script setup lang="ts">
32
+ // sub-parser wiring vueConfig() sets up, so it errors on every .vue file's
33
+ // <template> block. Those errors are swallowed rather than surfaced as lint
34
+ // failures, but they also mean a walk that needs to build a .vue file's
35
+ // export map (rather than lint it directly) can't see past it. A cycle
36
+ // that runs through a .vue file still gets caught when ESLint lints that
37
+ // .vue file itself, since it parses that one file with the real language
38
+ // config, not the export map builder.
39
+ 'import-x/extensions': ['.js', '.mjs', '.cjs', '.ts', '.tsx'],
40
+ },
41
+ },
42
+ ]
43
+ }
44
+
45
+ // The default groups list omits 'internal' (the rank an aliased import gets once it
46
+ // resolves), which would otherwise drop every @client/@server/@shared-style import to
47
+ // last place instead of sorting with 'external' the way Vue 3 projects conventionally
48
+ // write them. 'builtin' stays its own rank, ahead of both: a `node:*` import belongs at
49
+ // the top of the file the same way it already does under the default groups.
50
+ /** @returns {import('eslint').Linter.Config[]} Flat config entries for import ordering, aware of resolved aliases. */
51
+ export function importOrderConfig() {
52
+ return [
53
+ {
54
+ name: 'storefront/v3/import-order',
55
+ rules: {
56
+ 'import-x/order': [
57
+ 'error',
58
+ {
59
+ groups: [
60
+ 'builtin',
61
+ ['external', 'internal'],
62
+ 'parent',
63
+ 'sibling',
64
+ 'index',
65
+ ],
66
+ },
67
+ ],
68
+ },
69
+ },
70
+ ]
71
+ }
@@ -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
+ }