js-style-kit 0.6.1 → 0.7.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.
Files changed (64) hide show
  1. package/README.md +5 -0
  2. package/dist/index.d.ts +11 -5
  3. package/dist/index.js +48 -7
  4. package/dist/index.js.map +1 -1
  5. package/package.json +8 -5
  6. package/src/eslint/base/README.md +186 -0
  7. package/src/eslint/base/config.ts +37 -0
  8. package/src/eslint/base/rules.ts +444 -0
  9. package/src/eslint/base/types.ts +20 -0
  10. package/src/eslint/constants.ts +52 -0
  11. package/src/eslint/convex/README.md +30 -0
  12. package/src/eslint/convex/config.ts +34 -0
  13. package/src/eslint/convex/rules.ts +8 -0
  14. package/src/eslint/convex/types.ts +8 -0
  15. package/src/eslint/ignores.ts +31 -0
  16. package/src/eslint/import/README.md +397 -0
  17. package/src/eslint/import/config.ts +48 -0
  18. package/src/eslint/import/rules.ts +81 -0
  19. package/src/eslint/index.ts +259 -0
  20. package/src/eslint/jsdoc/README.md +399 -0
  21. package/src/eslint/jsdoc/config.ts +29 -0
  22. package/src/eslint/jsdoc/rules.ts +81 -0
  23. package/src/eslint/jsdoc/types.ts +56 -0
  24. package/src/eslint/nextjs/config.ts +25 -0
  25. package/src/eslint/nextjs/rules.ts +25 -0
  26. package/src/eslint/nextjs/types.ts +27 -0
  27. package/src/eslint/perfectionist/README.md +454 -0
  28. package/src/eslint/perfectionist/config.ts +25 -0
  29. package/src/eslint/perfectionist/rules.ts +39 -0
  30. package/src/eslint/prefer-arrow-function/config.ts +33 -0
  31. package/src/eslint/prefer-arrow-function/types.ts +13 -0
  32. package/src/eslint/process-custom-rules.ts +72 -0
  33. package/src/eslint/query/README.md +254 -0
  34. package/src/eslint/query/config.ts +27 -0
  35. package/src/eslint/query/rules.ts +11 -0
  36. package/src/eslint/query/types.ts +11 -0
  37. package/src/eslint/react/README.md +416 -0
  38. package/src/eslint/react/config.ts +65 -0
  39. package/src/eslint/react/rules.ts +188 -0
  40. package/src/eslint/react/types.ts +26 -0
  41. package/src/eslint/react-refresh/config.ts +28 -0
  42. package/src/eslint/react-refresh/rules.ts +48 -0
  43. package/src/eslint/storybook/README.md +424 -0
  44. package/src/eslint/storybook/config.ts +57 -0
  45. package/src/eslint/testing/README.md +436 -0
  46. package/src/eslint/testing/config.ts +90 -0
  47. package/src/eslint/testing/jest-rules.ts +47 -0
  48. package/src/eslint/testing/vitest-rules.ts +42 -0
  49. package/src/eslint/turbo/README.md +380 -0
  50. package/src/eslint/turbo/config.ts +26 -0
  51. package/src/eslint/turbo/types.ts +7 -0
  52. package/src/eslint/types.ts +29 -0
  53. package/src/eslint/typescript/README.md +229 -0
  54. package/src/eslint/typescript/config.ts +48 -0
  55. package/src/eslint/typescript/rules.ts +137 -0
  56. package/src/eslint/typescript/types.ts +35 -0
  57. package/src/eslint/unicorn/README.md +497 -0
  58. package/src/eslint/unicorn/config.ts +36 -0
  59. package/src/eslint/unicorn/rules.ts +86 -0
  60. package/src/index.ts +3 -0
  61. package/src/modules.d.ts +5 -0
  62. package/src/prettier/README.md +413 -0
  63. package/src/prettier/index.ts +110 -0
  64. package/src/utils/is-type.ts +60 -0
@@ -0,0 +1,39 @@
1
+ import type { EslintRuleConfig } from "../types.js";
2
+
3
+ interface PerfectionistRuleOptions {
4
+ order: "asc" | "desc";
5
+ type: "alphabetical" | "custom" | "line-length" | "natural";
6
+ }
7
+
8
+ const defaultOptions: PerfectionistRuleOptions = {
9
+ order: "asc",
10
+ type: "natural",
11
+ };
12
+
13
+ type PerfectionRules = Record<
14
+ `perfectionist/${string}`,
15
+ EslintRuleConfig<PerfectionistRuleOptions>
16
+ >;
17
+
18
+ export const perfectionistRules: PerfectionRules = {
19
+ "perfectionist/sort-array-includes": ["warn", defaultOptions],
20
+ "perfectionist/sort-classes": ["warn", defaultOptions],
21
+ "perfectionist/sort-decorators": ["warn", defaultOptions],
22
+ "perfectionist/sort-enums": ["warn", defaultOptions],
23
+ "perfectionist/sort-exports": ["warn", defaultOptions],
24
+ "perfectionist/sort-heritage-clauses": ["warn", defaultOptions],
25
+ "perfectionist/sort-imports": ["warn", defaultOptions],
26
+ "perfectionist/sort-interfaces": ["warn", defaultOptions],
27
+ "perfectionist/sort-intersection-types": ["warn", defaultOptions],
28
+ "perfectionist/sort-jsx-props": ["warn", defaultOptions],
29
+ "perfectionist/sort-maps": ["warn", defaultOptions],
30
+ "perfectionist/sort-modules": ["off", defaultOptions],
31
+ "perfectionist/sort-named-exports": ["warn", defaultOptions],
32
+ "perfectionist/sort-named-imports": ["warn", defaultOptions],
33
+ "perfectionist/sort-object-types": ["warn", defaultOptions],
34
+ "perfectionist/sort-objects": ["warn", defaultOptions],
35
+ "perfectionist/sort-sets": ["warn", defaultOptions],
36
+ "perfectionist/sort-switch-case": ["warn", defaultOptions],
37
+ "perfectionist/sort-union-types": ["warn", defaultOptions],
38
+ "perfectionist/sort-variable-declarations": ["warn", defaultOptions],
39
+ };
@@ -0,0 +1,33 @@
1
+ import type { ESLint } from "eslint";
2
+
3
+ import preferArrowFunctions from "eslint-plugin-prefer-arrow-functions";
4
+
5
+ import type { EslintConfigObject, EslintRuleConfig } from "../types.js";
6
+ import type { PreferArrowFunctionsRules } from "./types.js";
7
+
8
+ import { configNames } from "../constants.js";
9
+
10
+ /**
11
+ * Creates an ESLint configuration for Prefer Arrow Functions.
12
+ *
13
+ * @param customRules - Optional object containing custom rules to override or add to the configuration.
14
+ * @returns ESLint configuration object for Prefer Arrow Functions
15
+ */
16
+ export const preferArrowFunctionConfig = (
17
+ customRules?: Record<string, EslintRuleConfig>,
18
+ ): EslintConfigObject => ({
19
+ name: configNames.preferArrowFunction,
20
+ plugins: {
21
+ "prefer-arrow-functions": preferArrowFunctions as ESLint.Plugin,
22
+ },
23
+ rules: {
24
+ "prefer-arrow-functions/prefer-arrow-functions": [
25
+ "warn",
26
+ {
27
+ returnStyle: "unchanged",
28
+ singleReturnOnly: false,
29
+ },
30
+ ],
31
+ ...(customRules ?? {}),
32
+ } satisfies PreferArrowFunctionsRules,
33
+ });
@@ -0,0 +1,13 @@
1
+ import type { EslintRuleConfig } from "../types.js";
2
+
3
+ export interface PreferArrowFunctionsRules {
4
+ "prefer-arrow-functions/prefer-arrow-functions": EslintRuleConfig<{
5
+ allowedNames?: string[];
6
+ allowNamedFunctions?: boolean;
7
+ allowObjectProperties?: boolean;
8
+ classPropertiesAllowed?: boolean;
9
+ disallowPrototype?: boolean;
10
+ returnStyle?: "explicit" | "implicit" | "unchanged";
11
+ singleReturnOnly?: boolean;
12
+ }>;
13
+ }
@@ -0,0 +1,72 @@
1
+ import type { ConfigName } from "./constants.js";
2
+ import type { EslintRuleConfig } from "./types.js";
3
+
4
+ import { configNames, pluginPrefixMap } from "./constants.js";
5
+
6
+ /**
7
+ * Categorizes custom rules provided by the user based on plugin prefixes.
8
+ *
9
+ * @param customRules - The custom rules provided by the user.
10
+ * @returns An object where keys are config names (e.g., 'base', 'typescript')
11
+ * and values are the corresponding rule subsets for that config.
12
+ */
13
+ export const processCustomRules = (
14
+ customRules: Record<string, EslintRuleConfig>,
15
+ ): Partial<Record<ConfigName, Record<string, EslintRuleConfig>>> => {
16
+ // Initialize result object with all possible config categories
17
+ const categorizedRules = Object.values(configNames).reduce<
18
+ Record<ConfigName, Record<string, EslintRuleConfig>>
19
+ >(
20
+ (acc, configName) => {
21
+ acc[configName] = {};
22
+ return acc;
23
+ },
24
+ {} as Record<ConfigName, Record<string, EslintRuleConfig>>,
25
+ );
26
+
27
+ // Process each custom rule
28
+ for (const [ruleKey, ruleValue] of Object.entries(customRules)) {
29
+ // Quick check if rule has no prefix (no '/' or '@')
30
+ // Rules without prefixes go directly to base config
31
+ if (!ruleKey.includes("/") && !ruleKey.startsWith("@")) {
32
+ categorizedRules[configNames.base][ruleKey] = ruleValue;
33
+ continue;
34
+ }
35
+
36
+ // Extract the plugin prefix from the rule key
37
+ let prefix: null | string = null;
38
+
39
+ if (ruleKey.startsWith("@")) {
40
+ // Handle scoped packages like @typescript-eslint/rule-name
41
+ const firstSlashIndex = ruleKey.indexOf("/");
42
+ if (firstSlashIndex !== -1) {
43
+ prefix = ruleKey.substring(0, firstSlashIndex);
44
+ }
45
+ } else {
46
+ // Handle regular plugins like eslint-plugin-react/rule-name
47
+ const firstSlashIndex = ruleKey.indexOf("/");
48
+ if (firstSlashIndex !== -1) {
49
+ prefix = ruleKey.substring(0, firstSlashIndex);
50
+ }
51
+ }
52
+
53
+ // Find the corresponding config name for this prefix
54
+ const configName =
55
+ prefix ?
56
+ (pluginPrefixMap.get(prefix) ?? configNames.base)
57
+ : configNames.base;
58
+
59
+ // Add the rule to the appropriate config
60
+ categorizedRules[configName][ruleKey] = ruleValue;
61
+ }
62
+
63
+ // Filter out empty config objects to save memory
64
+ return Object.entries(categorizedRules).reduce<
65
+ Partial<Record<ConfigName, Record<string, EslintRuleConfig>>>
66
+ >((acc, [configName, rules]) => {
67
+ if (Object.keys(rules).length > 0) {
68
+ acc[configName as ConfigName] = rules;
69
+ }
70
+ return acc;
71
+ }, {});
72
+ };
@@ -0,0 +1,254 @@
1
+ # TanStack Query Configuration
2
+
3
+ ESLint rules for [TanStack Query](https://tanstack.com/query) (formerly React Query) to ensure proper usage patterns and prevent common mistakes.
4
+
5
+ [← Back to main README](../../../README.md)
6
+
7
+ ## Overview
8
+
9
+ TanStack Query support is **disabled by default** and provides:
10
+
11
+ - Query dependency validation
12
+ - Property ordering consistency
13
+ - Stable query client patterns
14
+ - Hook usage best practices
15
+
16
+ ## Quick Start
17
+
18
+ ```js
19
+ import { eslintConfig } from "js-style-kit";
20
+
21
+ export default eslintConfig({
22
+ query: true, // Enable TanStack Query rules
23
+ });
24
+ ```
25
+
26
+ ## When to Use
27
+
28
+ Enable this configuration when using TanStack Query in your project:
29
+
30
+ ```bash
31
+ npm install @tanstack/react-query
32
+ # or
33
+ yarn add @tanstack/react-query
34
+ # or
35
+ pnpm add @tanstack/react-query
36
+ # or
37
+ bun add @tanstack/react-query
38
+ ```
39
+
40
+ ## Rules
41
+
42
+ ### Dependency Validation
43
+
44
+ **`exhaustive-deps`**
45
+ Ensures all dependencies are declared in query keys and function scopes.
46
+
47
+ ```ts
48
+ // ✅ Good
49
+ const { data } = useQuery({
50
+ queryKey: ["users", userId], // userId in queryKey
51
+ queryFn: () => fetchUser(userId),
52
+ });
53
+
54
+ // ❌ Bad
55
+ const { data } = useQuery({
56
+ queryKey: ["users"], // Missing userId
57
+ queryFn: () => fetchUser(userId),
58
+ });
59
+ ```
60
+
61
+ **`no-unstable-deps`**
62
+ Prevents unstable references in query keys that could cause unnecessary refetches.
63
+
64
+ ```ts
65
+ // ✅ Good
66
+ const queryKey = useMemo(() => ["users", userId], [userId]);
67
+ const { data } = useQuery({
68
+ queryKey,
69
+ queryFn: fetchUsers,
70
+ });
71
+
72
+ // ❌ Bad
73
+ const { data } = useQuery({
74
+ queryKey: ["users", { id: userId }], // New object each render
75
+ queryFn: fetchUsers,
76
+ });
77
+ ```
78
+
79
+ ### Property Ordering
80
+
81
+ **`infinite-query-property-order`**
82
+ Enforces consistent property order in `useInfiniteQuery`:
83
+
84
+ ```ts
85
+ // ✅ Good - correct order
86
+ useInfiniteQuery({
87
+ queryKey: ["projects"],
88
+ queryFn: fetchProjects,
89
+ getNextPageParam: (lastPage) => lastPage.nextCursor,
90
+ initialPageParam: 0,
91
+ });
92
+ ```
93
+
94
+ **`mutation-property-order`**
95
+ Enforces consistent property order in `useMutation`:
96
+
97
+ ```ts
98
+ // ✅ Good - correct order
99
+ useMutation({
100
+ mutationFn: createUser,
101
+ onSuccess: () => {
102
+ /* ... */
103
+ },
104
+ onError: () => {
105
+ /* ... */
106
+ },
107
+ });
108
+ ```
109
+
110
+ ### Query Function Patterns
111
+
112
+ **`no-void-query-fn`**
113
+ Prevents `void` returning query functions:
114
+
115
+ ```ts
116
+ // ✅ Good
117
+ useQuery({
118
+ queryKey: ["users"],
119
+ queryFn: () => fetchUsers(), // Returns Promise<User[]>
120
+ });
121
+
122
+ // ❌ Bad
123
+ useQuery({
124
+ queryKey: ["users"],
125
+ queryFn: () => {
126
+ fetchUsers();
127
+ }, // Returns void
128
+ });
129
+ ```
130
+
131
+ **`no-rest-destructuring`**
132
+ Prevents rest destructuring which can lead to serialization issues:
133
+
134
+ ```ts
135
+ // ✅ Good
136
+ const { data, isLoading, error } = useQuery({
137
+ queryKey: ["users"],
138
+ queryFn: fetchUsers,
139
+ });
140
+
141
+ // ❌ Bad
142
+ const { data, ...rest } = useQuery({
143
+ queryKey: ["users"],
144
+ queryFn: fetchUsers,
145
+ });
146
+ ```
147
+
148
+ ### Client Stability
149
+
150
+ **`stable-query-client`**
151
+ Ensures QueryClient is stable across renders:
152
+
153
+ ```ts
154
+ // ✅ Good - client created outside component or with useMemo
155
+ const queryClient = new QueryClient();
156
+
157
+ function App() {
158
+ return (
159
+ <QueryClientProvider client={queryClient}>{/* ... */}</QueryClientProvider>
160
+ );
161
+ }
162
+
163
+ // ❌ Bad - new client every render
164
+ function App() {
165
+ const queryClient = new QueryClient();
166
+ return (
167
+ <QueryClientProvider client={queryClient}>{/* ... */}</QueryClientProvider>
168
+ );
169
+ }
170
+ ```
171
+
172
+ ## Common Patterns
173
+
174
+ ### With React and TypeScript
175
+
176
+ ```js
177
+ export default eslintConfig({
178
+ typescript: true,
179
+ react: { framework: "vite" },
180
+ query: true,
181
+ });
182
+ ```
183
+
184
+ ### With Next.js
185
+
186
+ ```js
187
+ export default eslintConfig({
188
+ typescript: true,
189
+ react: { framework: "next" },
190
+ query: true,
191
+ });
192
+ ```
193
+
194
+ ## Customization
195
+
196
+ Override specific Query rules:
197
+
198
+ ```js
199
+ export default eslintConfig({
200
+ query: true,
201
+ rules: {
202
+ // Disable property ordering if you prefer different order
203
+ "@tanstack/query/mutation-property-order": "off",
204
+
205
+ // Make exhaustive-deps an error instead of warning
206
+ "@tanstack/query/exhaustive-deps": "error",
207
+ },
208
+ });
209
+ ```
210
+
211
+ ## Best Practices
212
+
213
+ 1. **Always declare dependencies** in query keys
214
+ 2. **Use stable references** for query keys (memoize objects)
215
+ 3. **Keep query functions pure** - return data, don't mutate
216
+ 4. **Hoist QueryClient** creation outside components
217
+ 5. **Follow property order conventions** for consistency
218
+
219
+ ## Troubleshooting
220
+
221
+ ### False positives on exhaustive-deps
222
+
223
+ Ensure all variables used in `queryFn` are in `queryKey`:
224
+
225
+ ```ts
226
+ const { data } = useQuery({
227
+ queryKey: ["users", filter, sort], // Include all dependencies
228
+ queryFn: () => fetchUsers({ filter, sort }),
229
+ });
230
+ ```
231
+
232
+ ### Object key instability warnings
233
+
234
+ Memoize object keys:
235
+
236
+ ```ts
237
+ const params = useMemo(() => ({ filter, sort }), [filter, sort]);
238
+ const { data } = useQuery({
239
+ queryKey: ["users", params],
240
+ queryFn: () => fetchUsers(params),
241
+ });
242
+ ```
243
+
244
+ ## Related Configurations
245
+
246
+ - [React](../react/README.md) - React configuration
247
+ - [TypeScript](../typescript/README.md) - TypeScript support
248
+ - [Testing](../testing/README.md) - Test Query hooks
249
+
250
+ ## Learn More
251
+
252
+ - [TanStack Query ESLint Plugin](https://tanstack.com/query/latest/docs/eslint/eslint-plugin-query)
253
+ - [TanStack Query Documentation](https://tanstack.com/query/latest)
254
+ - [Main README](../../../README.md)
@@ -0,0 +1,27 @@
1
+ import type { ESLint } from "eslint";
2
+
3
+ import queryPlugin from "@tanstack/eslint-plugin-query";
4
+
5
+ import type { EslintConfigObject, EslintRuleConfig } from "../types.js";
6
+
7
+ import { configNames } from "../constants.js";
8
+ import { queryRules } from "./rules.js";
9
+
10
+ /**
11
+ * Creates an ESLint configuration for TanStack Query.
12
+ *
13
+ * @param customRules - Optional object containing custom rules to override or add to the Query configuration.
14
+ * @returns ESLint configuration object for TanStack Query
15
+ */
16
+ export const queryConfig = (
17
+ customRules?: Record<string, EslintRuleConfig>,
18
+ ): EslintConfigObject => ({
19
+ name: configNames.query,
20
+ plugins: {
21
+ "@tanstack/query": queryPlugin as unknown as ESLint.Plugin,
22
+ },
23
+ rules: {
24
+ ...queryRules,
25
+ ...(customRules ?? {}),
26
+ },
27
+ });
@@ -0,0 +1,11 @@
1
+ import type { QueryRules } from "./types.js";
2
+
3
+ export const queryRules: QueryRules = {
4
+ "@tanstack/query/exhaustive-deps": "warn",
5
+ "@tanstack/query/infinite-query-property-order": "warn",
6
+ "@tanstack/query/mutation-property-order": "warn",
7
+ "@tanstack/query/no-rest-destructuring": "warn",
8
+ "@tanstack/query/no-unstable-deps": "warn",
9
+ "@tanstack/query/no-void-query-fn": "warn",
10
+ "@tanstack/query/stable-query-client": "warn",
11
+ };
@@ -0,0 +1,11 @@
1
+ import type { EslintRuleConfig } from "../types.js";
2
+
3
+ export interface QueryRules {
4
+ "@tanstack/query/exhaustive-deps": EslintRuleConfig;
5
+ "@tanstack/query/infinite-query-property-order": EslintRuleConfig;
6
+ "@tanstack/query/mutation-property-order": EslintRuleConfig;
7
+ "@tanstack/query/no-rest-destructuring": EslintRuleConfig;
8
+ "@tanstack/query/no-unstable-deps": EslintRuleConfig;
9
+ "@tanstack/query/no-void-query-fn": EslintRuleConfig;
10
+ "@tanstack/query/stable-query-client": EslintRuleConfig;
11
+ }