@caspeco/eslint-config-react 5.0.0-beta.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/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # @caspeco/eslint-config-react
2
+
3
+ Caspeco ESLint configuration for React + TypeScript projects.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install --save-dev @caspeco/eslint-config-react
9
+ ```
10
+
11
+ ### Migrating from v4.x
12
+
13
+ If you're upgrading from `@caspeco/eslint-config` v4.x, use the automated migration script:
14
+
15
+ ```bash
16
+ # Preview changes
17
+ npx caspeco-react-migrate-v5 src/ --dry-run
18
+
19
+ # Apply changes
20
+ npx caspeco-react-migrate-v5 src/
21
+ ```
22
+
23
+ This updates the plugin namespace from `caspeco/` to `caspeco-react/`. See the [Migration Guide](../../MIGRATION.md) for details.
24
+
25
+ ## Usage
26
+
27
+ Create an `eslint.config.js` file in your project root:
28
+
29
+ ```js
30
+ import config from "@caspeco/eslint-config-react";
31
+
32
+ export default [{ ignores: ["dist/**/*", "build/**/*"] }, ...config];
33
+ ```
34
+
35
+ ## What's Included
36
+
37
+ This configuration includes everything from our TypeScript config plus React-specific rules:
38
+
39
+ ### Base TypeScript Configuration
40
+
41
+ - **TypeScript ESLint** - recommended-type-checked config
42
+ - **No Barrel Files** - Prevents barrel file usage
43
+ - **File Naming Conventions** - Enforces kebab-case for files and folders
44
+ - **Prettier Integration** - Compatible with Prettier formatting
45
+
46
+ ### React-Specific Configuration
47
+
48
+ - **React Plugin** - Core React linting rules
49
+ - **React Hooks** - Enforces rules of hooks
50
+ - **React Refresh** - Fast Refresh support for Vite/development
51
+ - **Caspeco Plugin** - Custom rules for Caspeco projects (Chakra UI import restrictions)
52
+
53
+ ## Peer Dependencies
54
+
55
+ This package requires the following peer dependencies (automatically installed by npm 7+):
56
+
57
+ - `eslint` >=9
58
+ - `eslint-plugin-react` >=7.37.0
59
+ - `eslint-plugin-react-hooks` >=7
60
+ - `eslint-plugin-react-refresh` >=0.4.0
61
+ - `globals` >=15
62
+
63
+ **Note:** Additional TypeScript-related peer dependencies are inherited from `@caspeco/eslint-config-ts` (installed automatically as a regular dependency).
64
+
65
+ ## Plugins
66
+
67
+ ### eslint-plugin-react
68
+
69
+ [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react)
70
+
71
+ Core React linting rules with the following customizations:
72
+
73
+ - `react-in-jsx-scope`: off (not needed in modern React)
74
+ - `prop-types`: off (using TypeScript instead)
75
+ - `display-name`: off
76
+
77
+ ### eslint-plugin-react-hooks
78
+
79
+ [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks)
80
+
81
+ Enforces:
82
+
83
+ - Rules of Hooks (error)
84
+ - Exhaustive dependencies (warning)
85
+
86
+ ### eslint-plugin-react-refresh
87
+
88
+ [eslint-plugin-react-refresh](https://github.com/ArnaudBarre/eslint-plugin-react-refresh)
89
+
90
+ Ensures components are compatible with Fast Refresh in development.
91
+
92
+ ### caspeco-react-plugin
93
+
94
+ Custom React-specific plugin with Caspeco rules:
95
+
96
+ - Discourages direct imports from `@chakra-ui/react` (use themed components instead)
97
+
98
+ ## Relationship with @caspeco/eslint-config-ts
99
+
100
+ This package depends on and extends `@caspeco/eslint-config-ts`, which is automatically installed as a dependency. This ensures React projects always use the same TypeScript rules without drift. If you're building a React project, use this package. For vanilla TypeScript projects, use `@caspeco/eslint-config-ts`.
101
+
102
+ ## License
103
+
104
+ MIT
package/bin/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # Migration Scripts
2
+
3
+ ## caspeco-react-migrate-v5
4
+
5
+ Automated migration script for updating from `@caspeco/eslint-config` v4.x to `@caspeco/eslint-config-react` v5.x.
6
+
7
+ ### Usage
8
+
9
+ ```bash
10
+ # Preview changes (dry run)
11
+ npx caspeco-react-migrate-v5 src/ --dry-run
12
+
13
+ # Apply changes
14
+ npx caspeco-react-migrate-v5 src/
15
+
16
+ # Scan entire project
17
+ npx caspeco-react-migrate-v5 .
18
+ ```
19
+
20
+ ### What it does
21
+
22
+ - Updates rule names: `caspeco/discourage-chakra-import` → `caspeco-react/discourage-chakra-import`
23
+ - Fixes ESLint disable comments
24
+ - Updates config files
25
+ - Processes `.js`, `.jsx`, `.ts`, `.tsx`, `.json`, and `.md` files
26
+
27
+ ### Files processed
28
+
29
+ The script automatically:
30
+
31
+ - Skips `node_modules`, `.git`, `dist`, `build`, and `coverage` directories
32
+ - Recursively processes all supported file types
33
+ - Shows a summary of changes made
34
+
35
+ ### Options
36
+
37
+ - `--dry-run`: Preview changes without modifying files
38
+ - First argument: Path to scan (defaults to current directory)
39
+
40
+ See the [Migration Guide](../../../MIGRATION.md) for full migration instructions.
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Migration script for v4.x to v5.x
5
+ *
6
+ * This script updates:
7
+ * - Package imports: @caspeco/eslint-config → @caspeco/eslint-config-react
8
+ * - Named imports: { reactConfig } → default import
9
+ * - Variable references: reactConfig → config
10
+ * - Rule names: caspeco/discourage-chakra-import → caspeco-react/discourage-chakra-import
11
+ * - ESLint disable comments
12
+ *
13
+ * Usage:
14
+ * npx caspeco-react-migrate-v5 [path] [--dry-run]
15
+ *
16
+ * Examples:
17
+ * npx caspeco-react-migrate-v5 src/
18
+ * npx caspeco-react-migrate-v5 . --dry-run
19
+ */
20
+
21
+ import fs from "fs";
22
+ import path from "path";
23
+ import { fileURLToPath } from "url";
24
+
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const __dirname = path.dirname(__filename);
27
+
28
+ const args = process.argv.slice(2);
29
+ const isDryRun = args.includes("--dry-run");
30
+ const targetPath = args.find((arg) => !arg.startsWith("--")) || ".";
31
+
32
+ // Patterns to search and replace
33
+ const replacements = [
34
+ {
35
+ pattern: /caspeco\/discourage-chakra-import/g,
36
+ replacement: "caspeco-react/discourage-chakra-import",
37
+ description: "Rule name",
38
+ },
39
+ ];
40
+
41
+ // Import transformation for eslint.config.js files
42
+ function transformImports(content, filename) {
43
+ if (!filename.includes("eslint.config.")) {
44
+ return { content, hasChanges: false };
45
+ }
46
+
47
+ let transformed = content;
48
+ let hasChanges = false;
49
+
50
+ // Replace import { reactConfig } from "@caspeco/eslint-config" → import config from "@caspeco/eslint-config-react"
51
+ const importPattern = /import\s*\{\s*reactConfig\s*\}\s*from\s*["']@caspeco\/eslint-config["']/;
52
+ if (importPattern.test(transformed)) {
53
+ transformed = transformed.replace(importPattern, 'import config from "@caspeco/eslint-config-react"');
54
+ hasChanges = true;
55
+
56
+ // If we changed the import, also update variable references
57
+ transformed = transformed.replace(/\breactConfig\b/g, "config");
58
+ }
59
+
60
+ return { content: transformed, hasChanges };
61
+ }
62
+
63
+ // File extensions to process
64
+ const extensions = [".js", ".jsx", ".ts", ".tsx", ".json", ".md", ".mjs", ".cjs"];
65
+
66
+ let filesProcessed = 0;
67
+ let filesChanged = 0;
68
+ let totalReplacements = 0;
69
+
70
+ /**
71
+ * Recursively process directory
72
+ */
73
+ function processDirectory(dir) {
74
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
75
+
76
+ for (const entry of entries) {
77
+ const fullPath = path.join(dir, entry.name);
78
+
79
+ // Skip node_modules, .git, dist, build directories
80
+ if (entry.isDirectory()) {
81
+ if (["node_modules", ".git", "dist", "build", "coverage"].includes(entry.name)) {
82
+ continue;
83
+ }
84
+ processDirectory(fullPath);
85
+ } else {
86
+ const ext = path.extname(entry.name);
87
+ if (extensions.includes(ext)) {
88
+ processFile(fullPath);
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Process a single file
96
+ */
97
+ function processFile(filePath) {
98
+ filesProcessed++;
99
+
100
+ let content;
101
+ try {
102
+ content = fs.readFileSync(filePath, "utf8");
103
+ } catch (err) {
104
+ console.error(`Error reading ${filePath}:`, err.message);
105
+ return;
106
+ }
107
+
108
+ let newContent = content;
109
+ let fileReplacements = 0;
110
+
111
+ // First, try import transformations for config files
112
+ const filename = path.basename(filePath);
113
+ const importResult = transformImports(newContent, filename);
114
+ if (importResult.hasChanges) {
115
+ newContent = importResult.content;
116
+ fileReplacements++; // Count as one logical replacement (import + references)
117
+ }
118
+
119
+ // Then apply all other replacement patterns
120
+ for (const { pattern, replacement } of replacements) {
121
+ const matches = newContent.match(pattern);
122
+ if (matches) {
123
+ fileReplacements += matches.length;
124
+ newContent = newContent.replace(pattern, replacement);
125
+ }
126
+ }
127
+
128
+ // If changes were made
129
+ if (fileReplacements > 0) {
130
+ filesChanged++;
131
+ totalReplacements += fileReplacements;
132
+
133
+ const relativePath = path.relative(process.cwd(), filePath);
134
+ console.log(
135
+ `${isDryRun ? "[DRY RUN] " : ""}✓ ${relativePath} (${fileReplacements} replacement${fileReplacements > 1 ? "s" : ""})`,
136
+ );
137
+
138
+ if (!isDryRun) {
139
+ try {
140
+ fs.writeFileSync(filePath, newContent, "utf8");
141
+ } catch (err) {
142
+ console.error(`Error writing ${filePath}:`, err.message);
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Main execution
150
+ */
151
+ function main() {
152
+ console.log("Caspeco Plugin Migration Tool");
153
+ console.log("==============================\n");
154
+
155
+ if (isDryRun) {
156
+ console.log("🔍 Running in DRY RUN mode - no files will be modified\n");
157
+ }
158
+
159
+ const resolvedPath = path.resolve(targetPath);
160
+
161
+ if (!fs.existsSync(resolvedPath)) {
162
+ console.error(`Error: Path "${targetPath}" does not exist`);
163
+ process.exit(1);
164
+ }
165
+
166
+ console.log(`Scanning: ${resolvedPath}\n`);
167
+
168
+ const stat = fs.statSync(resolvedPath);
169
+ if (stat.isDirectory()) {
170
+ processDirectory(resolvedPath);
171
+ } else {
172
+ processFile(resolvedPath);
173
+ }
174
+
175
+ console.log("\nSummary:");
176
+ console.log("========");
177
+ console.log(`Files scanned: ${filesProcessed}`);
178
+ console.log(`Files with changes: ${filesChanged}`);
179
+ console.log(`Total replacements: ${totalReplacements}`);
180
+
181
+ if (isDryRun && filesChanged > 0) {
182
+ console.log("\n💡 Run without --dry-run to apply changes");
183
+ } else if (filesChanged > 0) {
184
+ console.log("\n✅ Migration complete!");
185
+ } else {
186
+ console.log("\n✨ No files needed migration");
187
+ }
188
+ }
189
+
190
+ main();
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@caspeco/eslint-config-react",
3
+ "version": "5.0.0-beta.0",
4
+ "description": "Caspeco ESLint configuration for React projects",
5
+ "keywords": [
6
+ "eslint",
7
+ "eslint-config",
8
+ "react",
9
+ "typescript",
10
+ "caspeco"
11
+ ],
12
+ "homepage": "https://github.com/Caspeco/eslint-config#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/Caspeco/eslint-config/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/Caspeco/eslint-config",
19
+ "directory": "packages/eslint-config-react"
20
+ },
21
+ "license": "MIT",
22
+ "type": "module",
23
+ "main": "src/index.js",
24
+ "bin": {
25
+ "caspeco-react-migrate-v5": "./bin/caspeco-react-migrate-v5.js"
26
+ },
27
+ "files": [
28
+ "src",
29
+ "bin"
30
+ ],
31
+ "scripts": {
32
+ "test": "vitest"
33
+ },
34
+ "dependencies": {
35
+ "@caspeco/eslint-config-ts": "*"
36
+ },
37
+ "devDependencies": {
38
+ "@caspeco/test-utils": "*"
39
+ },
40
+ "peerDependencies": {
41
+ "eslint": ">=9",
42
+ "eslint-plugin-react": ">=7.37.0",
43
+ "eslint-plugin-react-hooks": ">=7",
44
+ "eslint-plugin-react-refresh": ">=0.4.0",
45
+ "globals": ">=15"
46
+ },
47
+ "engines": {
48
+ "node": ">=24",
49
+ "npm": ">=11.5.1"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ }
54
+ }
package/src/index.js ADDED
@@ -0,0 +1,74 @@
1
+ import tsConfig from "@caspeco/eslint-config-ts";
2
+ import reactRefresh from "eslint-plugin-react-refresh";
3
+ import reactPlugin from "eslint-plugin-react";
4
+ import reactHooksPlugin from "eslint-plugin-react-hooks";
5
+ import caspecoReactPlugin from "./plugins/caspeco-react.js";
6
+ import globals from "globals";
7
+
8
+ /** @type {import('@typescript-eslint/utils').TSESLint.FlatConfig.ConfigArray} */
9
+ const flatConfig = [
10
+ // Extend the vanilla TypeScript config
11
+ ...tsConfig,
12
+ // React plugin recommended config
13
+ {
14
+ ...reactPlugin.configs.flat?.recommended,
15
+ files: ["**/*.ts", "**/*.tsx", "**/*.jsx"],
16
+ settings: {
17
+ react: {
18
+ version: "detect",
19
+ },
20
+ },
21
+ languageOptions: {
22
+ ...reactPlugin.configs.flat?.recommended.languageOptions,
23
+ globals: {
24
+ ...globals.serviceworker,
25
+ ...globals.browser,
26
+ },
27
+ parserOptions: {
28
+ ...reactPlugin.configs.flat?.recommended.languageOptions?.parserOptions,
29
+ ecmaVersion: "latest",
30
+ },
31
+ },
32
+ rules: {
33
+ ...reactPlugin.configs.flat?.recommended.rules,
34
+ "react/react-in-jsx-scope": "off",
35
+ "react/prop-types": "off",
36
+ "react/display-name": "off",
37
+ },
38
+ },
39
+ // React hooks plugin recommended config
40
+ {
41
+ ...reactHooksPlugin.configs.recommended,
42
+ plugins: { "react-hooks": reactHooksPlugin },
43
+ files: ["**/*.ts", "**/*.tsx", "**/*.jsx"],
44
+ rules: {
45
+ "react-hooks/rules-of-hooks": "error",
46
+ "react-hooks/exhaustive-deps": "warn",
47
+ // We ignore the compiler specific rules as we need React 19 for this.
48
+ // https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks#flat-config-eslintconfigjsts-1
49
+ // When we support this, we can add:
50
+ // ...reactHooksPlugin.configs.recommended.rules,
51
+ },
52
+ },
53
+ // React refresh plugin config
54
+ {
55
+ ...reactRefresh.configs.recommended,
56
+ files: ["**/*.ts", "**/*.tsx", "**/*.jsx"],
57
+ rules: {
58
+ ...reactRefresh.configs.recommended.rules,
59
+ "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
60
+ },
61
+ },
62
+ // Caspeco React custom rules
63
+ {
64
+ files: ["**/*.ts", "**/*.tsx", "**/*.jsx"],
65
+ plugins: {
66
+ "caspeco-react": caspecoReactPlugin,
67
+ },
68
+ rules: {
69
+ "caspeco-react/discourage-chakra-import": "error",
70
+ },
71
+ },
72
+ ];
73
+
74
+ export default flatConfig;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @typedef {import("eslint").Rule.RuleModule} Rule
3
+ */
4
+
5
+ /**
6
+ * @type {{ rules: { [key: string]: Rule } }}
7
+ */
8
+ const caspeco = {
9
+ rules: {
10
+ "discourage-chakra-import": {
11
+ meta: {
12
+ type: "suggestion",
13
+ docs: {
14
+ description:
15
+ 'Discourage importing anything from "@chakra-ui/*". Use imports from "@caspeco/casper-ui-library" instead.',
16
+ category: "Best Practices",
17
+ url: "https://caspeco.github.io/eslint-config/#/plugins/caspeco?id=caspecodiscourage-chakra-import",
18
+ recommended: false,
19
+ },
20
+ messages: {
21
+ restrictedImport: "Importing from {{importPath}} is discouraged.",
22
+ },
23
+ schema: [],
24
+ },
25
+ create(context) {
26
+ return {
27
+ ImportDeclaration(node) {
28
+ const importPath = node.source.value;
29
+ if (typeof importPath === "string" && importPath.startsWith("@chakra-ui/")) {
30
+ context.report({
31
+ node,
32
+ messageId: "restrictedImport",
33
+ data: {
34
+ importPath,
35
+ },
36
+ });
37
+ }
38
+ },
39
+ };
40
+ },
41
+ },
42
+ },
43
+ };
44
+
45
+ export default caspeco;