@scayle/eslint-config-storefront 5.1.1 → 5.2.1
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 +25 -0
- package/README.md +213 -5
- package/config/rules.mjs +2 -0
- package/config/v3/__fixtures__/require-lazy-promise/file.ts +1 -0
- package/config/v3/__fixtures__/require-lazy-promise/tsconfig.json +10 -0
- package/config/v3/requireLazyPromise.mjs +127 -0
- package/config/v3/requireLazyPromise.test.mjs +170 -0
- package/config/v3/tailwind.mjs +2 -0
- package/config/v3/vueI18n.mjs +3 -1
- package/config/v3.mjs +20 -7
- package/config/v3.test.mjs +54 -0
- package/package.json +10 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @aboutyou/eslint-config-sfc
|
|
2
2
|
|
|
3
|
+
## 5.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependency `@typescript-eslint/utils@^8.70.0` to `@typescript-eslint/utils@^8.71.0`
|
|
8
|
+
- Updated dependency `eslint-plugin-jsdoc@^64.2.1` to `eslint-plugin-jsdoc@^65.0.0`
|
|
9
|
+
- Updated dependency `typescript-eslint@^8.70.0` to `typescript-eslint@^8.71.0`
|
|
10
|
+
- Updates `eslint-plugin-jsdoc` to v65 and aligns `typescript-eslint`, `@typescript-eslint/utils`, and `@typescript-eslint/rule-tester` on `^8.71.0`. The config doesn't enable the two `eslint-plugin-jsdoc` rules whose behavior changed in v65 (`check-indentation` and `check-line-alignment`), so existing lint setups are unaffected unless they enable those rules themselves. Keeping the `@typescript-eslint` packages on one version avoids duplicate copies of the AST types in a consumer's `node_modules`, which can break type checks on custom rules.
|
|
11
|
+
|
|
12
|
+
## 5.2.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- Adds a new `require-lazy-promise-in-view-props` rule to the V3 entry point (`storefrontV3()`), enabled at `error`. It flags a promise created inside a controller's `getViewProps()` that isn't wrapped in `lazyPromise` and isn't awaited or returned immediately, catching both wasted fetches on partial reloads and unhandled promise rejections.
|
|
17
|
+
|
|
18
|
+
This rule needs real type information to work, and it reads that from the same `resolver.tsconfigPath` option consumers already set for the import resolver. Because ESLint flat config merges type information across every config block matching a file, every consumer that sets `resolver.tsconfigPath` now gets real type-aware linting from `eslint-plugin-sonarjs` across their whole TypeScript codebase, not just from this new rule. `eslint-plugin-sonarjs`'s type-aware rules (`sonarjs/deprecation`, `sonarjs/different-types-comparison`, `sonarjs/no-redundant-optional`, and others) previously ran without type information and silently found nothing; they now report real findings. Existing consumers should expect to see and fix new `sonarjs` errors the first time they lint after upgrading.
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Added dependency `@typescript-eslint/utils@^8.70.0`
|
|
23
|
+
- Added dependency `ts-api-utils@^2.1.2`
|
|
24
|
+
- Updated dependency `eslint-plugin-unicorn@^74.0.0` to `eslint-plugin-unicorn@^76.0.0`
|
|
25
|
+
- Updated dependency `typescript-eslint@^8.67.0` to `typescript-eslint@^8.70.0`
|
|
26
|
+
- Speed up the V3 flat config. `@intlify/vue-i18n/no-unused-keys` now excludes test and story files from its scan, since they don't introduce translation key usages beyond what source files already have. `sonarjs/aws-restricted-ip-admin-access` and `better-tailwindcss/no-deprecated-classes` are turned off: the former only fires on AWS CDK code, the latter only on Tailwind v3 class names, and the `/v3` entry point is Tailwind v4 only.
|
|
27
|
+
|
|
3
28
|
## 5.1.1
|
|
4
29
|
|
|
5
30
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -11,6 +11,28 @@ Should you require support for `eslint@8`, please use `@scayle/eslint-config-sto
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## Entry points
|
|
15
|
+
|
|
16
|
+
The package exposes three entry points, all built on the same shared rule set. Pick the one
|
|
17
|
+
that matches your stack:
|
|
18
|
+
|
|
19
|
+
| Entry point | Export | For | Adds on top of the shared rules |
|
|
20
|
+
| ----------- | --------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| Default | `@scayle/eslint-config-storefront` | Framework-agnostic TypeScript/Node.js packages | JavaScript, TypeScript, JSDoc, and import rules |
|
|
22
|
+
| Nuxt | `@scayle/eslint-config-storefront/nuxt` | Nuxt projects — this is what **Storefront Application V2** uses, since V2 is a Nuxt 4 application | Nothing beyond the shared rules; `@nuxt/eslint` already supplies JavaScript, TypeScript, JSDoc, and import rules |
|
|
23
|
+
| V3 | `@scayle/eslint-config-storefront/v3` | The **Storefront Application V3** stack (Hono + Inertia.js + Vue 3, _not_ Nuxt) | Everything the default entry point adds, plus Vue language wiring, Vue rules, accessibility rules, `vue-i18n` linting, `@scayle/vue-composable` rules, Storybook file conventions, Tailwind CSS class validation, a TypeScript-aware import resolver, and the `require-lazy-promise-in-view-props` rule |
|
|
24
|
+
|
|
25
|
+
There is no `/v2` entry point: Storefront Application V2 is a Nuxt application, so it uses the
|
|
26
|
+
`/nuxt` entry point directly, the same as any other Nuxt project. "V2" and "V3" describe the two
|
|
27
|
+
Storefront Application stacks this monorepo ships, not two separate ESLint configs — V2 maps to
|
|
28
|
+
`/nuxt`, V3 maps to `/v3`.
|
|
29
|
+
|
|
30
|
+
Internally, `/nuxt` and `/v3` both build on the same base config, so a rule fixed or added to the
|
|
31
|
+
shared set (JavaScript, Node.js, JSON/JSONC, test files, SonarJS, Promise usage, modernization,
|
|
32
|
+
regex safety, comments hygiene) applies to every entry point without duplicating configuration.
|
|
33
|
+
Only `/v3` and the default entry point add TypeScript, JSDoc, and import rules directly — `/nuxt`
|
|
34
|
+
relies on `@nuxt/eslint` for those instead.
|
|
35
|
+
|
|
14
36
|
## Install
|
|
15
37
|
|
|
16
38
|
Add the `@scayle/eslint-config-storefront` manually to your project:
|
|
@@ -28,7 +50,7 @@ import storefront from '@scayle/eslint-config-storefront'
|
|
|
28
50
|
export default storefront()
|
|
29
51
|
```
|
|
30
52
|
|
|
31
|
-
### Nuxt projects
|
|
53
|
+
### Nuxt projects (Storefront Application V2)
|
|
32
54
|
|
|
33
55
|
Nuxt projects already get JavaScript, TypeScript, JSDoc, and import rules from
|
|
34
56
|
[`@nuxt/eslint`](https://eslint.nuxt.com/). Use the `/nuxt` entry point instead, which
|
|
@@ -43,12 +65,15 @@ import withNuxt from './.nuxt/eslint.config.mjs'
|
|
|
43
65
|
export default withNuxt(storefrontNuxt())
|
|
44
66
|
```
|
|
45
67
|
|
|
68
|
+
This is the entry point Storefront Application V2's own `eslint.config.mjs` uses.
|
|
69
|
+
|
|
46
70
|
### Storefront Application V3 projects
|
|
47
71
|
|
|
48
72
|
The Storefront Application V3 stack (Hono + Inertia.js + Vue 3, not Nuxt) should use the
|
|
49
73
|
`/v3` entry point. It builds on the default config and adds Vue language wiring, Vue rules,
|
|
50
74
|
accessibility rules, `vue-i18n` linting, `@scayle/vue-composable` rules, Storybook file
|
|
51
|
-
conventions, Tailwind CSS class validation,
|
|
75
|
+
conventions, Tailwind CSS class validation, a TypeScript-aware import resolver, and the
|
|
76
|
+
`require-lazy-promise-in-view-props` rule:
|
|
52
77
|
|
|
53
78
|
```js
|
|
54
79
|
// eslint.config.mjs
|
|
@@ -70,14 +95,76 @@ validate Tailwind classes or i18n keys without a real path. Optional options:
|
|
|
70
95
|
- `typescript.tsconfigPath`: absolute path to a `tsconfig.json`. Enables type-aware
|
|
71
96
|
TypeScript rules and points the import resolver at that file instead of
|
|
72
97
|
`<root>/tsconfig.json`
|
|
73
|
-
- `resolver.tsconfigPath`: absolute path to a `tsconfig.json` for the import resolver
|
|
74
|
-
|
|
75
|
-
|
|
98
|
+
- `resolver.tsconfigPath`: absolute path to a `tsconfig.json` for the import resolver and,
|
|
99
|
+
with the same `?? typescript.tsconfigPath` fallback, for type-checking the
|
|
100
|
+
`require-lazy-promise-in-view-props` rule below — independent of `typescript.tsconfigPath`
|
|
101
|
+
in the sense that setting it does not switch on `@typescript-eslint/*` type-checked rules
|
|
102
|
+
(`recommendedTypeChecked`) generally. Use this instead of `typescript.tsconfigPath` when you
|
|
103
|
+
want the resolver fix and the lazy-promise rule without also switching on
|
|
104
|
+
`@typescript-eslint/*` type-checked rules project-wide. When neither option resolves a
|
|
105
|
+
tsconfig path, `require-lazy-promise-in-view-props` is omitted entirely rather than
|
|
106
|
+
registered without type information. Setting it DOES have one other real effect: because
|
|
107
|
+
ESLint flat config merges type information across every config block matching a file, and
|
|
108
|
+
the lazy-promise rule's own block matches every `.ts`/`.tsx`/`.mts`/`.cts` file (not just
|
|
109
|
+
files with a `getViewProps` method), `eslint-plugin-sonarjs`'s type-aware rules gain real
|
|
110
|
+
type information project-wide too — they silently report nothing without a type checker, so
|
|
111
|
+
expect new `sonarjs` findings the first time you set this option, not just wherever the
|
|
112
|
+
lazy-promise rule itself looks
|
|
76
113
|
- `i18n.messageSyntaxVersion`: `vue-i18n` message syntax version, defaults to `^11.2.8`
|
|
77
114
|
- `i18n.unusedKeyIgnores`: regex-string patterns for translation keys used dynamically in code
|
|
78
115
|
- `vue.undefComponentIgnores`: custom element (web component) tag names to exempt from `vue/no-undef-components`
|
|
79
116
|
- `vueComposable.fallthroughAttrsIgnores`: path fragments to exempt from `require-explicit-fallthrough-attrs` (e.g. router-mounted page/layout roots that never receive fallthrough attributes)
|
|
80
117
|
|
|
118
|
+
This is the entry point Storefront Application V3's own `eslint.config.mjs` uses.
|
|
119
|
+
|
|
120
|
+
#### Vue rules and accessibility
|
|
121
|
+
|
|
122
|
+
`/v3` wires the Vue parser (including `<script setup lang="ts">`) via `vue-eslint-parser`, applies
|
|
123
|
+
`eslint-plugin-vue`'s essential, strongly-recommended, and recommended presets, and layers a
|
|
124
|
+
handful of Storefront-specific overrides on top (e.g. `vue/custom-event-name-casing`,
|
|
125
|
+
`vue/block-order`, styling rules disabled where they conflict with this project's Prettier
|
|
126
|
+
config). `vue-eslint-parser` doesn't reuse the type-aware `@typescript-eslint/no-unused-vars`
|
|
127
|
+
config from the TypeScript rules, since that one only scopes to `.ts`/`.tsx`/`.mts`/`.cts` files,
|
|
128
|
+
so `.vue` files get their own registration of the same rule.
|
|
129
|
+
|
|
130
|
+
Vue accessibility rules (`eslint-plugin-vuejs-accessibility`'s recommended preset) are wired but
|
|
131
|
+
every rule ships `off` by default. That baseline reflects that a11y coverage is opt-in until a
|
|
132
|
+
consuming project runs its own audit and turns rules on deliberately — it is not a statement that
|
|
133
|
+
accessibility doesn't matter.
|
|
134
|
+
|
|
135
|
+
#### `vue-i18n` linting
|
|
136
|
+
|
|
137
|
+
Validates message syntax, catches missing/unused/duplicate translation keys, and — only in
|
|
138
|
+
CI (`process.env.CI` set) — enforces that every locale file carries the same keys as the others
|
|
139
|
+
at `error` (a local `warn` lets you author a key in one locale before running the translation
|
|
140
|
+
step). Requires `i18n.localeDir` to be set; there is no default, since the plugin can't validate
|
|
141
|
+
anything without a real path to your locale files.
|
|
142
|
+
|
|
143
|
+
#### `@scayle/vue-composable` rules
|
|
144
|
+
|
|
145
|
+
Enables all five rules from the sibling
|
|
146
|
+
[`@scayle/eslint-plugin-vue-composable`](../eslint-plugin-vue-composable) package at `error`:
|
|
147
|
+
`no-composable-after-await`, `no-lifecycle-after-await`, `no-watch-after-await`,
|
|
148
|
+
`no-computed-after-await` (composables/lifecycle hooks/watchers/computed refs called after an
|
|
149
|
+
`await` inside a composable bind to the wrong instance), and
|
|
150
|
+
`require-explicit-fallthrough-attrs` (multi-root components must declare `inheritAttrs: false`,
|
|
151
|
+
since Vue can't infer where fallthrough attributes should land). See that package's own README
|
|
152
|
+
for the full rationale and examples per rule.
|
|
153
|
+
|
|
154
|
+
#### Storybook conventions
|
|
155
|
+
|
|
156
|
+
Applies `eslint-plugin-storybook`'s recommended preset to files matching `*.stories.*` /
|
|
157
|
+
`*.story.*`. No configuration needed.
|
|
158
|
+
|
|
159
|
+
#### Tailwind CSS class validation
|
|
160
|
+
|
|
161
|
+
Validates Tailwind utility classes via `eslint-plugin-better-tailwindcss`'s recommended preset,
|
|
162
|
+
against the CSS entry point you provide in `tailwind.entryPoint`. Class-order and
|
|
163
|
+
line-wrapping rules are disabled, since this project's Prettier Tailwind plugin already handles
|
|
164
|
+
sorting and would otherwise conflict with the ESLint plugin's own ordering opinion.
|
|
165
|
+
`no-deprecated-classes` is disabled too: the `/v3` entry point targets Tailwind v4, so this
|
|
166
|
+
rule's v3 class-name findings don't apply.
|
|
167
|
+
|
|
81
168
|
#### Import resolution
|
|
82
169
|
|
|
83
170
|
`/v3` wires `eslint-import-resolver-typescript`, so `import-x` rules resolve `tsconfig.json`
|
|
@@ -103,6 +190,64 @@ directory instead. Set `resolver.tsconfigPath` to an absolute path (for example,
|
|
|
103
190
|
`new URL('./tsconfig.json', import.meta.url).pathname` in your `eslint.config.mjs`) if your
|
|
104
191
|
editor's linting needs to match the command-line result.
|
|
105
192
|
|
|
193
|
+
#### `require-lazy-promise-in-view-props`
|
|
194
|
+
|
|
195
|
+
Enforces the `lazyPromise` convention in Inertia controllers: a promise created inside a
|
|
196
|
+
controller's `getViewProps()` method must be wrapped in the SDK's `lazyPromise(() => ...)`
|
|
197
|
+
helper, unless it's awaited or returned in the same statement it's created in. This closes
|
|
198
|
+
two related problems that a promise created eagerly in `getViewProps()` can cause: a
|
|
199
|
+
partial reload that excludes every prop consuming that promise still triggers the fetch
|
|
200
|
+
(wasted work), and an uncaught rejection on such a promise can surface as an unhandled
|
|
201
|
+
promise rejection. The rule reports at `error` and offers a suggestion (not an autofix,
|
|
202
|
+
since call sites also need to change from a bare reference to an accessor call, `x` → `x()`)
|
|
203
|
+
that wraps the flagged expression.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
// Before — flagged
|
|
207
|
+
const productsPromise = fetchProducts(params)
|
|
208
|
+
return {
|
|
209
|
+
products: async () => (await productsPromise).items,
|
|
210
|
+
pagination: async () => (await productsPromise).pagination,
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// After — the suggested fix, plus updating both call sites
|
|
214
|
+
const getProducts = lazyPromise(() => fetchProducts(params))
|
|
215
|
+
return {
|
|
216
|
+
products: async () => (await getProducts()).items,
|
|
217
|
+
pagination: async () => (await getProducts()).pagination,
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The rule is type-aware: it detects any thenable-typed local (not just a literal `Promise`
|
|
222
|
+
name match), so a `PromiseLike`-shaped or branded return type is caught too. It only checks
|
|
223
|
+
locals declared directly in `getViewProps()`'s own body — a promise created and consumed
|
|
224
|
+
entirely inside a nested function (a prop callback, a nested class's method) is not flagged,
|
|
225
|
+
since that promise is already effectively lazy: the enclosing function only runs when it's
|
|
226
|
+
invoked. A correctly-wrapped `lazyPromise(() => ...)` call itself returns a function, not a
|
|
227
|
+
thenable, so it never trips the check; passing `lazyPromise` an already-invoked promise
|
|
228
|
+
instead of a factory (`lazyPromise(fetchThing())` instead of
|
|
229
|
+
`lazyPromise(() => fetchThing())`) is a type error the TypeScript compiler catches on its
|
|
230
|
+
own — the wrong argument shape doesn't satisfy `lazyPromise`'s signature — so the rule
|
|
231
|
+
doesn't need its own check for that case.
|
|
232
|
+
|
|
233
|
+
Known limitations: the rule does not verify that the `lazyPromise` identifier it sees
|
|
234
|
+
resolves to the SDK's own export — a same-named local declaration would also satisfy it. It
|
|
235
|
+
likewise doesn't verify that a consuming prop callback actually chains the accessor's result
|
|
236
|
+
into its own return value; a callback that calls the accessor without awaiting or returning
|
|
237
|
+
it could still produce an unhandled rejection the rule won't catch. It also doesn't track
|
|
238
|
+
references across sibling declarators in the same body, so two independent promises
|
|
239
|
+
consumed together via `Promise.all([a, b])` are each flagged individually even though
|
|
240
|
+
neither needs `lazyPromise` there.
|
|
241
|
+
|
|
242
|
+
Requires `resolver.tsconfigPath` or `typescript.tsconfigPath` to be set — see
|
|
243
|
+
[Import resolution](#import-resolution) above. When neither is set, the rule is omitted
|
|
244
|
+
entirely from the config rather than registered without type information, which would
|
|
245
|
+
otherwise crash ESLint on the first `.ts` file it lints. Note that `resolver.tsconfigPath`
|
|
246
|
+
is passed to `parserOptions.projectService.defaultProject`, which is a fallback for files
|
|
247
|
+
not covered by an auto-discovered `tsconfig.json` — and which typescript-eslint hard-fails
|
|
248
|
+
on after 8 unmatched files by design. If your project's own `tsconfig.json` doesn't cover
|
|
249
|
+
every `.ts` file this rule lints, you may see an ESLint crash rather than a lint error.
|
|
250
|
+
|
|
106
251
|
### Add script for package.json
|
|
107
252
|
|
|
108
253
|
For example:
|
|
@@ -149,6 +294,69 @@ Add the following settings to your `.vscode/settings.json`:
|
|
|
149
294
|
}
|
|
150
295
|
```
|
|
151
296
|
|
|
297
|
+
## What the shared rules cover
|
|
298
|
+
|
|
299
|
+
Every entry point (default, `/nuxt`, `/v3`) includes the following rule domains. They live in
|
|
300
|
+
`config/rules.mjs`, `config/javascript.mjs`, `config/typescript.mjs`, `config/unicorn.mjs`,
|
|
301
|
+
`config/test.mjs`, and `config/ignores.mjs` in this package's source, one factory function per
|
|
302
|
+
domain:
|
|
303
|
+
|
|
304
|
+
- **Comments hygiene** (`eslint-comments/*`): no aggregating/duplicate/unlimited
|
|
305
|
+
`eslint-disable` comments, no unused `eslint-enable`.
|
|
306
|
+
- **Node.js** (`node/*`): deprecated API usage, callback error-handling conventions,
|
|
307
|
+
`process`/`Buffer` globals preferred over the bare identifiers.
|
|
308
|
+
- **JSON/JSONC** (`jsonc/*`): structural validity plus formatting rules for `.json`,
|
|
309
|
+
`.json5`, and `.jsonc` files.
|
|
310
|
+
- **Test files** (`vitest/*`, `no-only-tests/*`): `it`/`describe` conventions, no
|
|
311
|
+
duplicate test titles, no accidental `.only` left in committed code (`error` in CI,
|
|
312
|
+
`warn` locally so you can use `.only` while debugging).
|
|
313
|
+
- **SonarJS** (`sonarjs/*`): the plugin's recommended preset, with `no-duplicate-string`
|
|
314
|
+
off, `todo-tag`/`fixme-tag` downgraded to `warn`, and `aws-restricted-ip-admin-access`
|
|
315
|
+
off (it only fires on AWS CDK infrastructure code).
|
|
316
|
+
- **Promise usage** (`promise/*`): a curated subset of `eslint-plugin-promise`'s rules —
|
|
317
|
+
`no-multiple-resolved`, `no-new-statics`, and `no-return-wrap` at `error`, the rest at
|
|
318
|
+
`warn`. `catch-or-return` and `always-return` only inspect a promise used as a bare
|
|
319
|
+
statement, so a project whose promises are all stored in a variable before being awaited
|
|
320
|
+
or returned (as V3's controller code is) can safely turn both `off` in its own
|
|
321
|
+
`eslint.config.mjs` — Storefront Application V3's own config does exactly that.
|
|
322
|
+
(`require-lazy-promise-in-view-props` is what enforces the equivalent convention for V3
|
|
323
|
+
controllers; see above.)
|
|
324
|
+
- **Modernization** (`unicorn/*`, `e18e/*`): a curated subset of `eslint-plugin-unicorn`
|
|
325
|
+
(not its full `recommended` preset, which is hundreds of largely stylistic rules), plus
|
|
326
|
+
the [e18e](https://e18e.dev) community's rule set for modernization, module-replacement,
|
|
327
|
+
and performance findings. Every `e18e/*` rule ships at `warn` — the plugin is still
|
|
328
|
+
pre-1.0 and its rule set hasn't been reviewed against every consuming project yet, so a
|
|
329
|
+
warning surfaces a finding without failing your build. See
|
|
330
|
+
[`e18e/*` rules ship as warnings](#e18e-rules-ship-as-warnings) below for the one rule
|
|
331
|
+
worth knowing about specifically.
|
|
332
|
+
- **Regex safety** (`regexp/*`): `eslint-plugin-regexp`'s recommended preset, including
|
|
333
|
+
ReDoS-prone pattern detection.
|
|
334
|
+
|
|
335
|
+
The default and `/v3` entry points additionally include:
|
|
336
|
+
|
|
337
|
+
- **JavaScript** (`@eslint/js`'s recommended preset plus Storefront-specific additions):
|
|
338
|
+
strict equality, no implicit globals, consistent `const`/`let` usage, and similar
|
|
339
|
+
correctness rules that apply regardless of TypeScript.
|
|
340
|
+
- **TypeScript** (`@typescript-eslint/*`): the plugin's recommended preset, upgraded to
|
|
341
|
+
`recommendedTypeChecked` when a `tsconfigPath` is provided (see
|
|
342
|
+
[Storefront Application V3 projects](#storefront-application-v3-projects) above for how
|
|
343
|
+
V3 supplies one), plus Storefront-specific overrides — consistent type-only imports,
|
|
344
|
+
`interface` over `type` for object shapes, unused-variable detection that allows a
|
|
345
|
+
leading underscore to mark an intentionally-unused binding.
|
|
346
|
+
- **JSDoc** (`jsdoc/*`): validates JSDoc comment structure (parameter names, return
|
|
347
|
+
descriptions, alignment) at `warn`. This package is published and consumed by external
|
|
348
|
+
developers, so exported symbols are expected to carry meaningful documentation.
|
|
349
|
+
- **Import correctness** (`import-x/*`): duplicate imports, mutable exports, self-imports,
|
|
350
|
+
extraneous dependencies not declared in `package.json`, and consistent type-import
|
|
351
|
+
specifier style.
|
|
352
|
+
- **Ignore patterns**: build artifacts, lockfiles, and tooling caches (`node_modules`,
|
|
353
|
+
`dist`, `.output`, coverage reports, and similar) are excluded, on top of whatever your
|
|
354
|
+
project's own `.gitignore` excludes.
|
|
355
|
+
|
|
356
|
+
`/nuxt` omits all five of the above: `@nuxt/eslint` already provides JavaScript, TypeScript,
|
|
357
|
+
JSDoc, and import rules for a Nuxt project, and Nuxt's own tooling manages its build-output
|
|
358
|
+
ignore patterns.
|
|
359
|
+
|
|
152
360
|
## Customization
|
|
153
361
|
|
|
154
362
|
### Default config / `@scayle/eslint-config-storefront`
|
package/config/rules.mjs
CHANGED
|
@@ -89,6 +89,8 @@ const SONARJS_RULES = {
|
|
|
89
89
|
'sonarjs/no-duplicate-string': 'off',
|
|
90
90
|
'sonarjs/todo-tag': 'warn',
|
|
91
91
|
'sonarjs/fixme-tag': 'warn',
|
|
92
|
+
// Flags AWS CDK infrastructure code, which none of our projects use.
|
|
93
|
+
'sonarjs/aws-restricted-ip-admin-access': 'off',
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
/** @returns {import('eslint').Linter.Config[]} Flat config entries for SonarJS rules. */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils'
|
|
2
|
+
import * as tsutils from 'ts-api-utils'
|
|
3
|
+
|
|
4
|
+
const createRule = ESLintUtils.RuleCreator(
|
|
5
|
+
() =>
|
|
6
|
+
'https://github.com/scayle/eslint-config-storefront/blob/main/config/v3/requireLazyPromise.mjs',
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
const rule = createRule({
|
|
10
|
+
name: 'require-lazy-promise-in-view-props',
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'problem',
|
|
13
|
+
docs: {
|
|
14
|
+
description:
|
|
15
|
+
'Require promises created in getViewProps() to be wrapped in lazyPromise unless awaited or returned immediately.',
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
hasSuggestions: true,
|
|
19
|
+
messages: {
|
|
20
|
+
requireLazyPromise:
|
|
21
|
+
'Wrap this promise in `lazyPromise(() => ...)` so it only runs when a prop actually consumes it. A partial reload that excludes every consuming prop otherwise still triggers this fetch, and an uncaught rejection here can become an unhandled promise rejection.',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultOptions: [],
|
|
25
|
+
create(context) {
|
|
26
|
+
const services = ESLintUtils.getParserServices(context)
|
|
27
|
+
const checker = services.program.getTypeChecker()
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
VariableDeclarator(node) {
|
|
31
|
+
const init = node.init
|
|
32
|
+
if (init == null) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Only flag declarators directly inside getViewProps's own body. A
|
|
37
|
+
// promise inside a nested function (a prop callback, a nested method)
|
|
38
|
+
// is already lazy by construction, since that function only runs
|
|
39
|
+
// when invoked — reporting there would be a false positive.
|
|
40
|
+
const owner = context.sourceCode.getScope(node).variableScope.block
|
|
41
|
+
if (
|
|
42
|
+
owner.type !== 'FunctionExpression' ||
|
|
43
|
+
owner.parent.type !== 'MethodDefinition' ||
|
|
44
|
+
owner.parent.key.type !== 'Identifier' ||
|
|
45
|
+
owner.parent.key.name !== 'getViewProps'
|
|
46
|
+
) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// A correctly-wrapped `lazyPromise(() => ...)` call returns a
|
|
51
|
+
// function, not a thenable, so the type check below already skips
|
|
52
|
+
// it — no separate call-shape check needed.
|
|
53
|
+
const tsNode = services.esTreeNodeToTSNodeMap.get(init)
|
|
54
|
+
if (
|
|
55
|
+
!tsutils.isThenableType(
|
|
56
|
+
checker,
|
|
57
|
+
tsNode,
|
|
58
|
+
checker.getTypeAtLocation(tsNode),
|
|
59
|
+
)
|
|
60
|
+
) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
context.report({
|
|
65
|
+
node: init,
|
|
66
|
+
messageId: 'requireLazyPromise',
|
|
67
|
+
suggest: [
|
|
68
|
+
{
|
|
69
|
+
messageId: 'requireLazyPromise',
|
|
70
|
+
fix: (fixer) =>
|
|
71
|
+
fixer.replaceText(
|
|
72
|
+
init,
|
|
73
|
+
`lazyPromise(() => ${context.sourceCode.getText(init)})`,
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
})
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Enables the `require-lazy-promise-in-view-props` rule. This rule requires type
|
|
85
|
+
* information, so it is a no-op (returns no config entries) when `tsconfigPath`
|
|
86
|
+
* cannot be resolved — registering the rule without type information would crash
|
|
87
|
+
* ESLint's entire run on the first `.ts` file, rather than degrading gracefully.
|
|
88
|
+
* @param {string} [tsconfigPath] Absolute path to the tsconfig.json used as the
|
|
89
|
+
* default project for files not covered by an auto-discovered tsconfig.
|
|
90
|
+
* @returns {import('eslint').Linter.Config[]} Flat config entries for the require-lazy-promise
|
|
91
|
+
* rule, or an empty array when no tsconfig path is resolvable.
|
|
92
|
+
*/
|
|
93
|
+
export function requireLazyPromiseConfig(tsconfigPath) {
|
|
94
|
+
if (!tsconfigPath) {
|
|
95
|
+
return []
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
name: 'storefront/v3/require-lazy-promise',
|
|
101
|
+
files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
|
|
102
|
+
languageOptions: {
|
|
103
|
+
parserOptions: {
|
|
104
|
+
projectService: { defaultProject: tsconfigPath },
|
|
105
|
+
tsconfigRootDir: process.cwd(),
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
plugins: {
|
|
109
|
+
storefront: {
|
|
110
|
+
rules: {
|
|
111
|
+
// @typescript-eslint/utils RuleModuleWithName is compatible at runtime but
|
|
112
|
+
// structurally incompatible with ESLint's Rule.RuleModule; bridge via unknown cast
|
|
113
|
+
'require-lazy-promise-in-view-props':
|
|
114
|
+
/** @type {import('eslint').Rule.RuleModule} */ (
|
|
115
|
+
/** @type {unknown} */ (rule)
|
|
116
|
+
),
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
rules: {
|
|
121
|
+
'storefront/require-lazy-promise-in-view-props': 'error',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export default rule
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { RuleTester } from '@typescript-eslint/rule-tester'
|
|
4
|
+
import { describe, it } from 'vitest'
|
|
5
|
+
import rule from './requireLazyPromise.mjs'
|
|
6
|
+
|
|
7
|
+
RuleTester.describe = describe
|
|
8
|
+
RuleTester.it = it
|
|
9
|
+
// Required RuleTester wiring, not a left-in test filter: this assigns the reference so a test
|
|
10
|
+
// CASE can opt into `only: true`; it does not itself run any test with `.only`.
|
|
11
|
+
RuleTester.itOnly = it.only // eslint-disable-line no-only-tests/no-only-tests
|
|
12
|
+
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
14
|
+
const fixtureDir = path.join(__dirname, '__fixtures__', 'require-lazy-promise')
|
|
15
|
+
|
|
16
|
+
const ruleTester = new RuleTester({
|
|
17
|
+
languageOptions: {
|
|
18
|
+
parserOptions: {
|
|
19
|
+
project: path.join(fixtureDir, 'tsconfig.json'),
|
|
20
|
+
tsconfigRootDir: fixtureDir,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const filename = 'file.ts'
|
|
26
|
+
|
|
27
|
+
ruleTester.run('require-lazy-promise-in-view-props', rule, {
|
|
28
|
+
valid: [
|
|
29
|
+
{
|
|
30
|
+
name: 'lazyPromise-wrapped local',
|
|
31
|
+
filename,
|
|
32
|
+
code: `
|
|
33
|
+
declare function lazyPromise<T>(factory: () => Promise<T>): () => Promise<T>
|
|
34
|
+
declare function fetchThing(): Promise<string>
|
|
35
|
+
class C {
|
|
36
|
+
getViewProps() {
|
|
37
|
+
const getThing = lazyPromise(() => fetchThing())
|
|
38
|
+
return { thing: async () => await getThing() }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'promise awaited inline, never stored',
|
|
45
|
+
filename,
|
|
46
|
+
code: `
|
|
47
|
+
declare function fetchThing(): Promise<string>
|
|
48
|
+
class C {
|
|
49
|
+
async getViewProps() {
|
|
50
|
+
return { thing: await fetchThing() }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
`,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'non-thenable local is ignored',
|
|
57
|
+
filename,
|
|
58
|
+
code: `
|
|
59
|
+
class C {
|
|
60
|
+
getViewProps() {
|
|
61
|
+
const category = { id: 1 }
|
|
62
|
+
return { category }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
`,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'method not named getViewProps is ignored',
|
|
69
|
+
filename,
|
|
70
|
+
code: `
|
|
71
|
+
declare function fetchThing(): Promise<string>
|
|
72
|
+
class C {
|
|
73
|
+
getOtherProps() {
|
|
74
|
+
const thingPromise = fetchThing()
|
|
75
|
+
return { thing: async () => await thingPromise }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
`,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'promise created and consumed entirely inside a prop callback is not flagged (already lazy by construction)',
|
|
82
|
+
filename,
|
|
83
|
+
code: `
|
|
84
|
+
declare function fetchThing(): Promise<string>
|
|
85
|
+
class C {
|
|
86
|
+
getViewProps() {
|
|
87
|
+
return {
|
|
88
|
+
thing: async () => {
|
|
89
|
+
const inner = fetchThing()
|
|
90
|
+
return await inner
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
`,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
invalid: [
|
|
99
|
+
{
|
|
100
|
+
name: 'eager promise shared by two props',
|
|
101
|
+
filename,
|
|
102
|
+
code: `
|
|
103
|
+
declare function fetchThing(): Promise<{ a: string; b: string }>
|
|
104
|
+
class C {
|
|
105
|
+
getViewProps() {
|
|
106
|
+
const thingPromise = fetchThing()
|
|
107
|
+
return {
|
|
108
|
+
a: async () => (await thingPromise).a,
|
|
109
|
+
b: async () => (await thingPromise).b,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
`,
|
|
114
|
+
errors: [
|
|
115
|
+
{
|
|
116
|
+
messageId: 'requireLazyPromise',
|
|
117
|
+
suggestions: [
|
|
118
|
+
{
|
|
119
|
+
messageId: 'requireLazyPromise',
|
|
120
|
+
output: `
|
|
121
|
+
declare function fetchThing(): Promise<{ a: string; b: string }>
|
|
122
|
+
class C {
|
|
123
|
+
getViewProps() {
|
|
124
|
+
const thingPromise = lazyPromise(() => fetchThing())
|
|
125
|
+
return {
|
|
126
|
+
a: async () => (await thingPromise).a,
|
|
127
|
+
b: async () => (await thingPromise).b,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
`,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'eager promise with a .catch already attached is still flagged',
|
|
139
|
+
filename,
|
|
140
|
+
code: `
|
|
141
|
+
declare function fetchThing(): Promise<string | undefined>
|
|
142
|
+
class C {
|
|
143
|
+
getViewProps() {
|
|
144
|
+
const thingPromise = fetchThing().catch(() => undefined)
|
|
145
|
+
return { thing: async () => await thingPromise }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
`,
|
|
149
|
+
errors: [
|
|
150
|
+
{
|
|
151
|
+
messageId: 'requireLazyPromise',
|
|
152
|
+
suggestions: [
|
|
153
|
+
{
|
|
154
|
+
messageId: 'requireLazyPromise',
|
|
155
|
+
output: `
|
|
156
|
+
declare function fetchThing(): Promise<string | undefined>
|
|
157
|
+
class C {
|
|
158
|
+
getViewProps() {
|
|
159
|
+
const thingPromise = lazyPromise(() => fetchThing().catch(() => undefined))
|
|
160
|
+
return { thing: async () => await thingPromise }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
`,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
})
|
package/config/v3/tailwind.mjs
CHANGED
|
@@ -22,6 +22,8 @@ export function tailwindConfig(config) {
|
|
|
22
22
|
// The eslint plugin uses a different class order than the prettier plugin.
|
|
23
23
|
// Disabled to avoid conflicts between the two.
|
|
24
24
|
'better-tailwindcss/enforce-consistent-class-order': 'off',
|
|
25
|
+
// Flags Tailwind v3 class names. The /v3 entry point targets Tailwind v4 only.
|
|
26
|
+
'better-tailwindcss/no-deprecated-classes': 'off',
|
|
25
27
|
},
|
|
26
28
|
settings: {
|
|
27
29
|
'better-tailwindcss': { entryPoint },
|
package/config/v3/vueI18n.mjs
CHANGED
|
@@ -35,10 +35,12 @@ export function vueI18nConfig(config) {
|
|
|
35
35
|
'@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error',
|
|
36
36
|
'@intlify/vue-i18n/no-dynamic-keys': 'off',
|
|
37
37
|
// Performance-intensive: scans all matching files for unused translation keys.
|
|
38
|
+
// Excludes tests and stories, which don't introduce key usages beyond what
|
|
39
|
+
// source files already have, to cut the scanned file count roughly in half.
|
|
38
40
|
'@intlify/vue-i18n/no-unused-keys': [
|
|
39
41
|
'error',
|
|
40
42
|
{
|
|
41
|
-
src: './src/{client,server,shared}
|
|
43
|
+
src: './src/{client,server,shared}/**/!(*.test|*.stories).{vue,ts}',
|
|
42
44
|
extensions: ['.vue', '.ts'],
|
|
43
45
|
ignores: unusedKeyIgnores,
|
|
44
46
|
},
|
package/config/v3.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { storefront } from './index.mjs'
|
|
|
3
3
|
import { vueConfig } from './v3/vue.mjs'
|
|
4
4
|
import { vueI18nConfig } from './v3/vueI18n.mjs'
|
|
5
5
|
import { vueComposableConfig } from './v3/vueComposable.mjs'
|
|
6
|
+
import { requireLazyPromiseConfig } from './v3/requireLazyPromise.mjs'
|
|
6
7
|
import { storybookConfig } from './v3/storybook.mjs'
|
|
7
8
|
import { tailwindConfig } from './v3/tailwind.mjs'
|
|
8
9
|
import {
|
|
@@ -16,13 +17,22 @@ import {
|
|
|
16
17
|
* @property {string} [typescript.tsconfigPath] Enables type-aware TypeScript rules when set.
|
|
17
18
|
* @property {object} [resolver] Import resolver settings.
|
|
18
19
|
* @property {string} [resolver.tsconfigPath] Absolute path to the tsconfig.json the import
|
|
19
|
-
* resolver reads `paths` aliases from. Independent of `typescript.tsconfigPath`:
|
|
20
|
-
*
|
|
21
|
-
* to `typescript.tsconfigPath`, then to the
|
|
22
|
-
* which only resolves correctly when ESLint's
|
|
23
|
-
* root (true for `pnpm lint`/CI via
|
|
24
|
-
* extension). Set this explicitly, as an
|
|
25
|
-
* location, if ESLint might ever run with a
|
|
20
|
+
* resolver reads `paths` aliases from. Independent of `typescript.tsconfigPath`: it does not
|
|
21
|
+
* switch on `tseslint.configs.recommendedTypeChecked` or any other `@typescript-eslint/*`
|
|
22
|
+
* type-checked rule set. Omit to fall back to `typescript.tsconfigPath`, then to the
|
|
23
|
+
* resolver's own `<cwd>/tsconfig.json` default, which only resolves correctly when ESLint's
|
|
24
|
+
* working directory happens to be this project's root (true for `pnpm lint`/CI via
|
|
25
|
+
* Turborepo, not guaranteed for an editor's ESLint extension). Set this explicitly, as an
|
|
26
|
+
* absolute path anchored to the config file's own location, if ESLint might ever run with a
|
|
27
|
+
* different working directory. Also used, with the same `?? typescript?.tsconfigPath`
|
|
28
|
+
* fallback, by `requireLazyPromiseConfig` to type-check the `require-lazy-promise-in-view-props`
|
|
29
|
+
* rule; when neither option resolves, that rule is omitted entirely rather than registered
|
|
30
|
+
* without type information. Setting this DOES have one other real effect: the type
|
|
31
|
+
* information `requireLazyPromiseConfig` attaches applies, via ESLint flat config's
|
|
32
|
+
* per-file `languageOptions` merging, to every `.ts`/`.tsx`/`.mts`/`.cts` file in the
|
|
33
|
+
* project (its own block matches `**\/*.ts` broadly, not just files with a `getViewProps`
|
|
34
|
+
* method) — so `eslint-plugin-sonarjs`'s type-aware rules, which silently no-op without a
|
|
35
|
+
* type checker, start reporting real findings project-wide the first time this option is set.
|
|
26
36
|
* @property {object} tailwind Tailwind CSS settings.
|
|
27
37
|
* @property {string} tailwind.entryPoint Path to the project's Tailwind CSS entry point.
|
|
28
38
|
* @property {object} i18n `vue-i18n` linting settings.
|
|
@@ -60,6 +70,9 @@ export const storefrontV3 = function (config) {
|
|
|
60
70
|
...vueConfig(vue),
|
|
61
71
|
...vueI18nConfig(i18n),
|
|
62
72
|
...vueComposableConfig(vueComposable),
|
|
73
|
+
...requireLazyPromiseConfig(
|
|
74
|
+
resolver?.tsconfigPath ?? typescript?.tsconfigPath,
|
|
75
|
+
),
|
|
63
76
|
...importResolverConfig(resolver?.tsconfigPath ?? typescript?.tsconfigPath),
|
|
64
77
|
...importOrderConfig(),
|
|
65
78
|
)
|
package/config/v3.test.mjs
CHANGED
|
@@ -125,4 +125,58 @@ describe('storefrontV3', () => {
|
|
|
125
125
|
}).toConfigs(),
|
|
126
126
|
).resolves.toBeInstanceOf(Array)
|
|
127
127
|
})
|
|
128
|
+
|
|
129
|
+
it('wires the require-lazy-promise-in-view-props rule when resolver.tsconfigPath is set', async () => {
|
|
130
|
+
const config = storefrontV3({
|
|
131
|
+
tailwind: { entryPoint: './src/client/index.css' },
|
|
132
|
+
i18n: { localeDir: './src/i18n/locales/*.json' },
|
|
133
|
+
resolver: { tsconfigPath: '/absolute/resolver-tsconfig.json' },
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const resolved = await config.toConfigs()
|
|
137
|
+
const entry = resolved.find(
|
|
138
|
+
(c) => c.name === 'storefront/v3/require-lazy-promise',
|
|
139
|
+
)
|
|
140
|
+
if (!entry) {
|
|
141
|
+
throw new Error('require-lazy-promise entry not found')
|
|
142
|
+
}
|
|
143
|
+
const { rules } = entry
|
|
144
|
+
if (!rules) {
|
|
145
|
+
throw new Error('require-lazy-promise entry has no rules')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
expect(rules['storefront/require-lazy-promise-in-view-props']).toBe('error')
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('wires the require-lazy-promise-in-view-props rule via typescript.tsconfigPath when resolver.tsconfigPath is unset', async () => {
|
|
152
|
+
const config = storefrontV3({
|
|
153
|
+
tailwind: { entryPoint: './src/client/index.css' },
|
|
154
|
+
i18n: { localeDir: './src/i18n/locales/*.json' },
|
|
155
|
+
typescript: { tsconfigPath: '/absolute/typescript-tsconfig.json' },
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const resolved = await config.toConfigs()
|
|
159
|
+
const entry = resolved.find(
|
|
160
|
+
(c) => c.name === 'storefront/v3/require-lazy-promise',
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
expect(entry).toBeDefined()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
// Registering the rule without type information would crash ESLint's entire
|
|
167
|
+
// run on the first .ts file, rather than reporting a lint error — omitting
|
|
168
|
+
// the entry is what makes an untyped project degrade gracefully instead.
|
|
169
|
+
it('omits the require-lazy-promise-in-view-props rule entirely when no tsconfig path is resolvable', async () => {
|
|
170
|
+
const config = storefrontV3({
|
|
171
|
+
tailwind: { entryPoint: './src/client/index.css' },
|
|
172
|
+
i18n: { localeDir: './src/i18n/locales/*.json' },
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
const resolved = await config.toConfigs()
|
|
176
|
+
const entry = resolved.find(
|
|
177
|
+
(c) => c.name === 'storefront/v3/require-lazy-promise',
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
expect(entry).toBeUndefined()
|
|
181
|
+
})
|
|
128
182
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scayle/eslint-config-storefront",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.1",
|
|
4
4
|
"description": "SCAYLE Commerce Engine specific eslint configuration",
|
|
5
5
|
"author": "SCAYLE Commerce Engine",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,13 +42,14 @@
|
|
|
42
42
|
"@eslint/js": "^10.0.0",
|
|
43
43
|
"@intlify/eslint-plugin-vue-i18n": "^4.5.1",
|
|
44
44
|
"@scayle/eslint-plugin-vue-composable": "1.2.1",
|
|
45
|
+
"@typescript-eslint/utils": "^8.71.0",
|
|
45
46
|
"@vitest/eslint-plugin": "^1.6.20",
|
|
46
47
|
"eslint-config-flat-gitignore": "^2.3.0",
|
|
47
48
|
"eslint-flat-config-utils": "^3.2.0",
|
|
48
49
|
"eslint-import-resolver-typescript": "^4.4.5",
|
|
49
50
|
"eslint-plugin-better-tailwindcss": "^4.7.0",
|
|
50
51
|
"eslint-plugin-import-x": "^4.17.1",
|
|
51
|
-
"eslint-plugin-jsdoc": "^
|
|
52
|
+
"eslint-plugin-jsdoc": "^65.0.0",
|
|
52
53
|
"eslint-plugin-jsonc": "^3.4.1",
|
|
53
54
|
"eslint-plugin-n": "^18.3.0",
|
|
54
55
|
"eslint-plugin-no-only-tests": "^3.4.0",
|
|
@@ -56,21 +57,23 @@
|
|
|
56
57
|
"eslint-plugin-regexp": "^3.2.0",
|
|
57
58
|
"eslint-plugin-sonarjs": "^4.2.0",
|
|
58
59
|
"eslint-plugin-storybook": "^10.5.8",
|
|
59
|
-
"eslint-plugin-unicorn": "^
|
|
60
|
+
"eslint-plugin-unicorn": "^76.0.0",
|
|
60
61
|
"eslint-plugin-vue": "^10.10.0",
|
|
61
62
|
"eslint-plugin-vuejs-accessibility": "^2.6.0",
|
|
62
63
|
"globals": "^17.7.0",
|
|
63
|
-
"
|
|
64
|
+
"ts-api-utils": "^2.1.2",
|
|
65
|
+
"typescript-eslint": "^8.71.0",
|
|
64
66
|
"vue-eslint-parser": "^10.4.1"
|
|
65
67
|
},
|
|
66
68
|
"devDependencies": {
|
|
67
69
|
"@types/node": "^24",
|
|
68
|
-
"@
|
|
69
|
-
"
|
|
70
|
+
"@typescript-eslint/rule-tester": "^8.71.0",
|
|
71
|
+
"@vitest/coverage-v8": "5.0.3",
|
|
72
|
+
"eslint": "10.11.0",
|
|
70
73
|
"eslint-formatter-gitlab": "7.2.0",
|
|
71
74
|
"publint": "0.3.24",
|
|
72
75
|
"typescript": "6.0.3",
|
|
73
|
-
"vitest": "
|
|
76
|
+
"vitest": "5.0.3"
|
|
74
77
|
},
|
|
75
78
|
"scripts": {
|
|
76
79
|
"lint": "eslint .",
|