@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.
- package/CHANGELOG.md +117 -0
- package/README.md +126 -43
- package/config/ignores.mjs +46 -0
- package/config/index.mjs +129 -0
- package/config/javascript.mjs +159 -0
- package/config/rules.mjs +220 -0
- package/config/test.mjs +36 -0
- package/config/typescript.mjs +92 -0
- package/config/unicorn.mjs +33 -0
- package/config/v3/importResolver.mjs +71 -0
- package/config/v3/storybook.mjs +10 -0
- package/config/v3/tailwind.mjs +30 -0
- package/config/v3/vue.mjs +153 -0
- package/config/v3/vueComposable.mjs +29 -0
- package/config/v3/vueI18n.mjs +52 -0
- package/config/v3.mjs +66 -0
- package/config/v3.test.mjs +128 -0
- package/index.mjs +3 -2
- package/package.json +37 -17
- package/storefront-nuxt.mjs +4 -0
- package/storefront-v3.mjs +4 -0
- package/config/flat/importX.mjs +0 -22
- package/config/flat/index.mjs +0 -155
- package/config/flat/promise.mjs +0 -28
- package/config/flat/sonarjs.mjs +0 -19
|
@@ -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
|
+
}
|
package/config/rules.mjs
ADDED
|
@@ -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
|
+
}
|
package/config/test.mjs
ADDED
|
@@ -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
|
+
}
|