js-style-kit 0.7.0 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-style-kit",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "A zero configuration style guide for ESLint and Prettier",
5
5
  "keywords": [
6
6
  "eslint",
@@ -42,22 +42,22 @@
42
42
  "typecheck": "tsc --noEmit"
43
43
  },
44
44
  "dependencies": {
45
- "@convex-dev/eslint-plugin": "0.0.1-alpha.4",
45
+ "@convex-dev/eslint-plugin": "1.0.0",
46
46
  "@prettier/plugin-oxc": "0.0.4",
47
47
  "@tanstack/eslint-plugin-query": "5.91.2",
48
- "@typescript-eslint/parser": "8.46.0",
49
- "eslint": "9.37.0",
48
+ "@typescript-eslint/parser": "8.46.2",
49
+ "eslint": "9.38.0",
50
50
  "eslint-import-resolver-typescript": "4.4.4",
51
51
  "eslint-plugin-import-x": "4.16.1",
52
52
  "eslint-plugin-jest": "29.0.1",
53
- "eslint-plugin-jsdoc": "61.1.4",
54
- "eslint-plugin-nextjs": "1.1.0",
53
+ "eslint-plugin-jsdoc": "61.1.5",
54
+ "eslint-plugin-nextjs": "1.1.1",
55
55
  "eslint-plugin-perfectionist": "4.15.1",
56
56
  "eslint-plugin-prefer-arrow-functions": "3.9.1",
57
57
  "eslint-plugin-react": "7.37.5",
58
58
  "eslint-plugin-react-hooks": "7.0.0",
59
59
  "eslint-plugin-react-refresh": "0.4.24",
60
- "eslint-plugin-storybook": "9.1.12",
60
+ "eslint-plugin-storybook": "9.1.13",
61
61
  "eslint-plugin-turbo": "2.5.8",
62
62
  "eslint-plugin-unicorn": "61.0.2",
63
63
  "eslint-plugin-vitest": "0.5.4",
@@ -67,8 +67,8 @@
67
67
  "prettier-plugin-curly": "0.3.2",
68
68
  "prettier-plugin-packagejson": "2.5.19",
69
69
  "prettier-plugin-sort-json": "4.1.1",
70
- "prettier-plugin-tailwindcss": "0.7.0",
71
- "typescript-eslint": "8.46.0"
70
+ "prettier-plugin-tailwindcss": "0.7.1",
71
+ "typescript-eslint": "8.46.2"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@repo/typescript-config": "workspace:*",
@@ -22,9 +22,10 @@ export default eslintConfig({
22
22
 
23
23
  ## File Scope
24
24
 
25
- Convex rules apply only to files in your `convex` directory.
25
+ Convex rules apply only to files in your `convex` directory: `**/convex/**/*.{ts,js}`
26
26
 
27
27
  ## Learn More
28
28
 
29
29
  - [Convex ESLint Plugin](https://docs.convex.dev/eslint) - Official documentation
30
+ - [Convex Validators](https://docs.convex.dev/functions/validation) - Argument validation guide
30
31
  - [Main README](../../../README.md)
@@ -2,7 +2,6 @@ import type { ConvexRules } from "./types.js";
2
2
 
3
3
  export const convexRules: ConvexRules = {
4
4
  "@convex-dev/import-wrong-runtime": "warn",
5
- "@convex-dev/no-args-without-validator": "warn",
6
- "@convex-dev/no-missing-args-validator": "warn",
7
5
  "@convex-dev/no-old-registered-function-syntax": "warn",
6
+ "@convex-dev/require-args-validator": "warn",
8
7
  };
@@ -2,7 +2,6 @@ import type { EslintRuleConfig } from "../types.js";
2
2
 
3
3
  export interface ConvexRules {
4
4
  "@convex-dev/import-wrong-runtime": EslintRuleConfig;
5
- "@convex-dev/no-args-without-validator": EslintRuleConfig;
6
- "@convex-dev/no-missing-args-validator": EslintRuleConfig;
7
5
  "@convex-dev/no-old-registered-function-syntax": EslintRuleConfig;
6
+ "@convex-dev/require-args-validator": EslintRuleConfig;
8
7
  }
@@ -1,5 +1,7 @@
1
1
  import type { Linter } from "eslint";
2
2
 
3
+ import type { ReactFramework } from "./types.js";
4
+
3
5
  import { configNames } from "./constants.js";
4
6
 
5
7
  /**
@@ -7,23 +9,24 @@ import { configNames } from "./constants.js";
7
9
  * By default, ignores node_modules, dist directories, and .git directories.
8
10
  *
9
11
  * @param options - Object with options to control the ignores configuration
10
- * @param options.userIgnores - Additional glob patterns to ignore in ESLint checks
11
- * @param options.next - Whether to include .next directory in ignores
12
+ * @param options.reactFramework - The React framework being used
12
13
  * @param options.storybook - Whether to include .storybook directory in ignores
14
+ * @param options.userIgnores - Additional glob patterns to ignore in ESLint checks
13
15
  * @returns ESLint configuration object with ignore patterns
14
16
  */
15
17
  export const ignoresConfig = ({
16
- next = false,
17
- storybook = false,
18
- userIgnores = [],
18
+ reactFramework,
19
+ storybook,
20
+ userIgnores,
19
21
  }: {
20
- next?: boolean;
21
- storybook?: boolean;
22
- userIgnores?: string[];
23
- } = {}): Linter.Config => ({
22
+ reactFramework: ReactFramework;
23
+ storybook: boolean;
24
+ userIgnores: string[];
25
+ }): Linter.Config => ({
24
26
  ignores: [
25
27
  "**/dist/",
26
- ...(next ? [".next"] : []),
28
+ ...(reactFramework === "next" ? [".next"] : []),
29
+ ...(reactFramework === "react-router" ? [".react-router"] : []),
27
30
  ...(storybook ? ["!.storybook"] : []),
28
31
  ...userIgnores,
29
32
  ],
@@ -1,6 +1,11 @@
1
1
  import type { Linter } from "eslint";
2
2
 
3
- import type { EslintRuleConfig, FilenameCase, FunctionStyle } from "./types.js";
3
+ import type {
4
+ EslintRuleConfig,
5
+ FilenameCase,
6
+ FunctionStyle,
7
+ ReactFramework,
8
+ } from "./types.js";
4
9
 
5
10
  import { isObject, isString } from "../utils/is-type.js";
6
11
  import { baseEslintConfig } from "./base/config.js";
@@ -22,14 +27,6 @@ import { turboConfig } from "./turbo/config.js";
22
27
  import { tseslintConfig } from "./typescript/config.js";
23
28
  import { unicornConfig } from "./unicorn/config.js";
24
29
 
25
- const defaultTestingConfig: TestingConfig = {
26
- filenamePattern: "test",
27
- files: ["**/*.{test,spec}.{ts,tsx,js,jsx}"],
28
- formattingRules: true,
29
- framework: "vitest",
30
- itOrTest: "it",
31
- };
32
-
33
30
  export interface EslintConfigOptions {
34
31
  convex?: boolean;
35
32
  functionStyle?: "off" | FunctionStyle;
@@ -44,7 +41,7 @@ export interface EslintConfigOptions {
44
41
  react?:
45
42
  | boolean
46
43
  | {
47
- framework?: "next" | "none" | "react-router" | "remix" | "vite";
44
+ framework?: ReactFramework;
48
45
  reactCompiler?: boolean;
49
46
  reactRefresh?: boolean;
50
47
  };
@@ -82,8 +79,9 @@ export interface EslintConfigOptions {
82
79
  * @param options.testing - An object with the following properties:
83
80
  * - `filenamePattern`: One of "spec" or "test" to determine which filename pattern to use.
84
81
  * - `files`: Array of file patterns to include in the configuration.
85
- * - `framework`: One of "vitest" or "jest" to determine which testing library to use.
82
+ * - `framework`: One of "vitest" or "jest" or "bun" or "node" to determine which testing library to use.
86
83
  * - `formattingRules`: Whether to include formatting rules like padding around blocks.
84
+ * - `importRestrictions`: Whether to enforce imports from the correct testing framework.
87
85
  * - `itOrTest`: One of "it" or "test" to determine which test function to use.
88
86
  * @param options.typescript - Whether to include TypeScript rules. Can be a boolean or a string with path to tsconfig.
89
87
  * @param options.turbo - Whether to include Turborepo rules. Defaults to false.
@@ -104,7 +102,7 @@ export const eslintConfig = (
104
102
  rules,
105
103
  sorting = true,
106
104
  storybook = false,
107
- testing = defaultTestingConfig,
105
+ testing,
108
106
  turbo = false,
109
107
  typescript = true,
110
108
  unicorn = { filenameCase: "kebabCase" },
@@ -114,11 +112,10 @@ export const eslintConfig = (
114
112
  // Categorize user's custom rules first
115
113
  const categorizedRules = rules === undefined ? {} : processCustomRules(rules);
116
114
 
117
- const usingNextjs = isObject(react) && react.framework === "next";
118
-
119
115
  const configs: Linter.Config[] = [
120
116
  ignoresConfig({
121
- next: usingNextjs,
117
+ reactFramework:
118
+ isObject(react) && react.framework ? react.framework : "none",
122
119
  storybook,
123
120
  userIgnores: ignores,
124
121
  }),
@@ -180,7 +177,7 @@ export const eslintConfig = (
180
177
  }),
181
178
  );
182
179
 
183
- if (usingNextjs) {
180
+ if (isObject(react) && react.framework === "next") {
184
181
  configs.push(nextjsConfig(categorizedRules[configNames.nextjs]));
185
182
  }
186
183
  }
@@ -194,6 +191,15 @@ export const eslintConfig = (
194
191
  }
195
192
 
196
193
  if (testing !== false) {
194
+ const defaultTestingConfig: TestingConfig = {
195
+ filenamePattern: "test",
196
+ files: ["**/*.{test,spec}.{ts,tsx,js,jsx}"],
197
+ formattingRules: true,
198
+ framework: "vitest",
199
+ importRestrictions: true,
200
+ itOrTest: "it",
201
+ };
202
+
197
203
  // Use the provided testing config or the default if testing is true
198
204
  const mergedTestingConfig: TestingConfig =
199
205
  isObject(testing) ?
@@ -201,8 +207,14 @@ export const eslintConfig = (
201
207
  : defaultTestingConfig;
202
208
 
203
209
  // Destructure from the merged config
204
- const { filenamePattern, files, formattingRules, framework, itOrTest } =
205
- mergedTestingConfig;
210
+ const {
211
+ filenamePattern,
212
+ files,
213
+ formattingRules,
214
+ framework,
215
+ importRestrictions,
216
+ itOrTest,
217
+ } = mergedTestingConfig;
206
218
 
207
219
  configs.push(
208
220
  testingConfig(
@@ -211,7 +223,9 @@ export const eslintConfig = (
211
223
  files,
212
224
  formattingRules,
213
225
  framework,
226
+ importRestrictions,
214
227
  itOrTest,
228
+ typescript: Boolean(typescript),
215
229
  },
216
230
  categorizedRules[configNames.testing],
217
231
  ),
@@ -4,6 +4,7 @@ import vitest from "eslint-plugin-vitest";
4
4
  import type { EslintConfigObject, EslintRuleConfig } from "../types.js";
5
5
 
6
6
  import { configNames } from "../constants.js";
7
+ import { getImportRestrictions } from "./get-import-restrictions.js";
7
8
  import { jestRules } from "./jest-rules.js";
8
9
  import { vitestRules } from "./vitest-rules.js";
9
10
 
@@ -12,7 +13,15 @@ export interface TestingConfig {
12
13
  files?: string[];
13
14
  formattingRules?: boolean;
14
15
  framework?: "bun" | "jest" | "node" | "vitest";
16
+ /**
17
+ * Whether to enforce imports from the correct testing framework.
18
+ * Uses the built-in ESLint `no-restricted-imports` rule.
19
+ *
20
+ * @default true
21
+ */
22
+ importRestrictions?: boolean;
15
23
  itOrTest?: "it" | "test";
24
+ typescript?: boolean;
16
25
  }
17
26
 
18
27
  /**
@@ -22,24 +31,23 @@ export interface TestingConfig {
22
31
  * @param options.files - Files to include in the configuration
23
32
  * @param options.filenamePattern - ".test" or ".spec" filename pattern
24
33
  * @param options.itOrTest - "it" or "test"
25
- * @param options.framework - "jest" or "vitest"
34
+ * @param options.framework - "jest" or "vitest" or "bun" or "node"
26
35
  * @param options.formattingRules - Whether to include formatting rules like padding around blocks
36
+ * @param options.importRestrictions - Whether to enforce imports from the correct testing framework
37
+ * @param options.typescript - Whether the user is using TypeScript
27
38
  * @param customRules - Optional object containing custom rules to override or add to the testing configuration.
28
39
  * @returns ESLint configuration object
29
40
  */
30
41
  export const testingConfig = (
31
42
  {
32
- filenamePattern,
43
+ filenamePattern = "test",
33
44
  files,
34
- formattingRules,
35
- framework,
36
- itOrTest,
37
- }: TestingConfig = {
38
- filenamePattern: "test",
39
- formattingRules: true,
40
- framework: "vitest",
41
- itOrTest: "test",
42
- },
45
+ formattingRules = true,
46
+ framework = "vitest",
47
+ importRestrictions = true,
48
+ itOrTest = "test",
49
+ typescript = true,
50
+ }: TestingConfig = {},
43
51
  customRules?: Record<string, EslintRuleConfig>,
44
52
  ): EslintConfigObject => ({
45
53
  files: files ?? ["**/*.{test,spec}.{ts,tsx,js,jsx}"],
@@ -55,8 +63,8 @@ export const testingConfig = (
55
63
  vitest,
56
64
  },
57
65
  rules: {
66
+ ...(typescript ? { "@typescript-eslint/unbound-method": "off" } : {}),
58
67
  // jest doesn't have a file name rule, so we'll use this one for both
59
- "@typescript-eslint/unbound-method": "off",
60
68
  "vitest/consistent-test-filename": [
61
69
  "warn",
62
70
  {
@@ -76,6 +84,7 @@ export const testingConfig = (
76
84
  "jest/padding-around-test-blocks": "warn",
77
85
  }
78
86
  : {}),
87
+ ...(importRestrictions ? getImportRestrictions(framework) : {}),
79
88
  ...(customRules ?? {}),
80
89
  },
81
90
  ...(framework !== "jest" && framework !== "vitest" ?
@@ -0,0 +1,70 @@
1
+ import type { EslintRuleConfig } from "../types.js";
2
+
3
+ const commonTestImports = [
4
+ "describe",
5
+ "it",
6
+ "test",
7
+ "expect",
8
+ "beforeAll",
9
+ "beforeEach",
10
+ "afterAll",
11
+ "afterEach",
12
+ "vi",
13
+ "mock",
14
+ "spyOn",
15
+ ];
16
+
17
+ const frameworkConfig = {
18
+ bun: {
19
+ allowed: "'bun:test'",
20
+ restricted: ["vitest", "jest", "@jest/globals", "node:test"],
21
+ },
22
+ jest: {
23
+ allowed: "'jest' or '@jest/globals'",
24
+ restricted: ["vitest", "bun:test", "node:test"],
25
+ },
26
+ node: {
27
+ allowed: "'node:test'",
28
+ restricted: ["vitest", "jest", "@jest/globals", "bun:test"],
29
+ },
30
+ vitest: {
31
+ allowed: "'vitest'",
32
+ restricted: ["jest", "@jest/globals", "bun:test", "node:test"],
33
+ },
34
+ } as const;
35
+
36
+ /**
37
+ * Generates the error message for restricted imports.
38
+ *
39
+ * @param allowedFramework - The allowed framework(s) for imports
40
+ * @returns The formatted error message
41
+ */
42
+ const getRestrictionMessage = (allowedFramework: string): string =>
43
+ `This project is setup to use ${allowedFramework} for testing. Importing from other testing frameworks is not allowed. Change this setting in eslint.config.js under testing.framework`;
44
+
45
+ /**
46
+ * Returns import restriction rules based on the testing framework.
47
+ * Prevents importing from the wrong testing framework.
48
+ *
49
+ * @param framework - The testing framework being used
50
+ * @returns ESLint rules object with import restrictions
51
+ */
52
+ export const getImportRestrictions = (
53
+ framework: "bun" | "jest" | "node" | "vitest",
54
+ ): Record<string, EslintRuleConfig> => {
55
+ const config = frameworkConfig[framework];
56
+ const message = getRestrictionMessage(config.allowed);
57
+
58
+ return {
59
+ "no-restricted-imports": [
60
+ "warn",
61
+ {
62
+ paths: config.restricted.map((name) => ({
63
+ importNames: commonTestImports,
64
+ message,
65
+ name,
66
+ })),
67
+ },
68
+ ],
69
+ };
70
+ };
@@ -27,3 +27,10 @@ export type FilenameCase =
27
27
  | "snakeCase";
28
28
 
29
29
  export type FunctionStyle = "arrow" | "declaration" | "expression";
30
+
31
+ export type ReactFramework =
32
+ | "next"
33
+ | "none"
34
+ | "react-router"
35
+ | "remix"
36
+ | "vite";