react-anti-pattern-sniffer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.snifferrc.json +29 -0
  2. package/LICENSE +21 -0
  3. package/README.md +289 -0
  4. package/bin/react-sniff.js +3 -0
  5. package/dist/src/cli/arg-parser.d.ts +10 -0
  6. package/dist/src/cli/arg-parser.d.ts.map +1 -0
  7. package/dist/src/cli/arg-parser.js +81 -0
  8. package/dist/src/cli/arg-parser.js.map +1 -0
  9. package/dist/src/cli/config-loader.d.ts +11 -0
  10. package/dist/src/cli/config-loader.d.ts.map +1 -0
  11. package/dist/src/cli/config-loader.js +140 -0
  12. package/dist/src/cli/config-loader.js.map +1 -0
  13. package/dist/src/cli/help.d.ts +3 -0
  14. package/dist/src/cli/help.d.ts.map +1 -0
  15. package/dist/src/cli/help.js +59 -0
  16. package/dist/src/cli/help.js.map +1 -0
  17. package/dist/src/cli/index.d.ts +2 -0
  18. package/dist/src/cli/index.d.ts.map +1 -0
  19. package/dist/src/cli/index.js +107 -0
  20. package/dist/src/cli/index.js.map +1 -0
  21. package/dist/src/core/file-discoverer.d.ts +8 -0
  22. package/dist/src/core/file-discoverer.d.ts.map +1 -0
  23. package/dist/src/core/file-discoverer.js +151 -0
  24. package/dist/src/core/file-discoverer.js.map +1 -0
  25. package/dist/src/core/orchestrator.d.ts +13 -0
  26. package/dist/src/core/orchestrator.d.ts.map +1 -0
  27. package/dist/src/core/orchestrator.js +176 -0
  28. package/dist/src/core/orchestrator.js.map +1 -0
  29. package/dist/src/core/sniffer-ignore.d.ts +25 -0
  30. package/dist/src/core/sniffer-ignore.d.ts.map +1 -0
  31. package/dist/src/core/sniffer-ignore.js +91 -0
  32. package/dist/src/core/sniffer-ignore.js.map +1 -0
  33. package/dist/src/core/sniffer-registry.d.ts +8 -0
  34. package/dist/src/core/sniffer-registry.d.ts.map +1 -0
  35. package/dist/src/core/sniffer-registry.js +64 -0
  36. package/dist/src/core/sniffer-registry.js.map +1 -0
  37. package/dist/src/core/worker-pool.d.ts +27 -0
  38. package/dist/src/core/worker-pool.d.ts.map +1 -0
  39. package/dist/src/core/worker-pool.js +176 -0
  40. package/dist/src/core/worker-pool.js.map +1 -0
  41. package/dist/src/core/worker-runner.d.ts +2 -0
  42. package/dist/src/core/worker-runner.d.ts.map +1 -0
  43. package/dist/src/core/worker-runner.js +52 -0
  44. package/dist/src/core/worker-runner.js.map +1 -0
  45. package/dist/src/output/formatter.d.ts +3 -0
  46. package/dist/src/output/formatter.d.ts.map +1 -0
  47. package/dist/src/output/formatter.js +13 -0
  48. package/dist/src/output/formatter.js.map +1 -0
  49. package/dist/src/output/json-renderer.d.ts +3 -0
  50. package/dist/src/output/json-renderer.d.ts.map +1 -0
  51. package/dist/src/output/json-renderer.js +49 -0
  52. package/dist/src/output/json-renderer.js.map +1 -0
  53. package/dist/src/output/markdown-renderer.d.ts +3 -0
  54. package/dist/src/output/markdown-renderer.d.ts.map +1 -0
  55. package/dist/src/output/markdown-renderer.js +70 -0
  56. package/dist/src/output/markdown-renderer.js.map +1 -0
  57. package/dist/src/plugins/plugin-loader.d.ts +7 -0
  58. package/dist/src/plugins/plugin-loader.d.ts.map +1 -0
  59. package/dist/src/plugins/plugin-loader.js +47 -0
  60. package/dist/src/plugins/plugin-loader.js.map +1 -0
  61. package/dist/src/plugins/plugin-sandbox.d.ts +3 -0
  62. package/dist/src/plugins/plugin-sandbox.d.ts.map +1 -0
  63. package/dist/src/plugins/plugin-sandbox.js +105 -0
  64. package/dist/src/plugins/plugin-sandbox.js.map +1 -0
  65. package/dist/src/plugins/plugin-validator.d.ts +14 -0
  66. package/dist/src/plugins/plugin-validator.d.ts.map +1 -0
  67. package/dist/src/plugins/plugin-validator.js +92 -0
  68. package/dist/src/plugins/plugin-validator.js.map +1 -0
  69. package/dist/src/sniffers/god-hook-sniffer.d.ts +12 -0
  70. package/dist/src/sniffers/god-hook-sniffer.d.ts.map +1 -0
  71. package/dist/src/sniffers/god-hook-sniffer.js +109 -0
  72. package/dist/src/sniffers/god-hook-sniffer.js.map +1 -0
  73. package/dist/src/sniffers/prop-drilling-sniffer.d.ts +5 -0
  74. package/dist/src/sniffers/prop-drilling-sniffer.d.ts.map +1 -0
  75. package/dist/src/sniffers/prop-drilling-sniffer.js +145 -0
  76. package/dist/src/sniffers/prop-drilling-sniffer.js.map +1 -0
  77. package/dist/src/sniffers/prop-explosion-sniffer.d.ts +4 -0
  78. package/dist/src/sniffers/prop-explosion-sniffer.d.ts.map +1 -0
  79. package/dist/src/sniffers/prop-explosion-sniffer.js +134 -0
  80. package/dist/src/sniffers/prop-explosion-sniffer.js.map +1 -0
  81. package/dist/src/sniffers/sniffer-interface.d.ts +88 -0
  82. package/dist/src/sniffers/sniffer-interface.d.ts.map +1 -0
  83. package/dist/src/sniffers/sniffer-interface.js +18 -0
  84. package/dist/src/sniffers/sniffer-interface.js.map +1 -0
  85. package/dist/src/tui/interactive-viewer.d.ts +7 -0
  86. package/dist/src/tui/interactive-viewer.d.ts.map +1 -0
  87. package/dist/src/tui/interactive-viewer.js +453 -0
  88. package/dist/src/tui/interactive-viewer.js.map +1 -0
  89. package/dist/src/utils/logger.d.ts +11 -0
  90. package/dist/src/utils/logger.d.ts.map +1 -0
  91. package/dist/src/utils/logger.js +90 -0
  92. package/dist/src/utils/logger.js.map +1 -0
  93. package/dist/src/utils/regex-helpers.d.ts +53 -0
  94. package/dist/src/utils/regex-helpers.d.ts.map +1 -0
  95. package/dist/src/utils/regex-helpers.js +275 -0
  96. package/dist/src/utils/regex-helpers.js.map +1 -0
  97. package/package.json +40 -0
@@ -0,0 +1,29 @@
1
+ {
2
+ "include": ["**/*.{jsx,tsx}"],
3
+ "exclude": ["node_modules", "dist", "build", "**/*.test.*", "**/*.spec.*"],
4
+ "parallel": true,
5
+ "maxWorkers": 4,
6
+ "timeoutMs": 30000,
7
+ "outputFormat": "markdown",
8
+ "outputPath": null,
9
+ "sniffers": {
10
+ "prop-explosion": {
11
+ "enabled": true,
12
+ "threshold": 7,
13
+ "severity": "warning"
14
+ },
15
+ "god-hook": {
16
+ "enabled": true,
17
+ "maxUseState": 4,
18
+ "maxUseEffect": 3,
19
+ "maxTotalHooks": 10,
20
+ "severity": "warning"
21
+ },
22
+ "prop-drilling": {
23
+ "enabled": true,
24
+ "severity": "warning",
25
+ "whitelistedProps": ["className", "style", "children", "key", "ref", "id", "data-testid"]
26
+ }
27
+ },
28
+ "plugins": []
29
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 react-anti-pattern-sniffer contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,289 @@
1
+ # react-anti-pattern-sniffer
2
+
3
+ Zero-dependency CLI tool that detects React anti-patterns in your codebase using regex-based heuristics and parallel worker threads.
4
+
5
+ ## Features
6
+
7
+ - **Zero runtime dependencies** — uses only Node.js built-ins
8
+ - **Three built-in sniffers**: prop explosion, god hook, prop drilling
9
+ - **Interactive TUI** — browse results, copy as AI prompt, ignore components
10
+ - **Batch mode** — focus on the first N issues at a time
11
+ - **Parallel execution** via `worker_threads` for fast analysis
12
+ - **Plugin system** — register custom sniffers with security validation
13
+ - **Markdown & JSON output** — human-readable reports or structured data
14
+ - **`.snifferignore`** — persistently ignore specific components
15
+ - **Git hook friendly** — exit codes for CI/CD and husky integration
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install --save-dev react-anti-pattern-sniffer
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```bash
26
+ # Scan current directory
27
+ npx react-sniff
28
+
29
+ # Short alias
30
+ npx ras
31
+
32
+ # Interactive mode — browse results in a TUI
33
+ ras -i
34
+
35
+ # Batch mode — show only the first 10 issues
36
+ ras -b 10
37
+
38
+ # Interactive + batch — browse first 20 issues in TUI
39
+ ras -i -b 20
40
+
41
+ # Output JSON report
42
+ react-sniff --format json --output report.json
43
+ ```
44
+
45
+ ## CLI Options
46
+
47
+ ```
48
+ Usage: react-sniff [options] [dir]
49
+
50
+ Options:
51
+ -d, --dir <path> Target directory (default: cwd)
52
+ -c, --config <path> Config file path (default: .snifferrc.json)
53
+ -s, --sniffers <list> Comma-separated sniffers to run
54
+ -f, --format <type> Output format: markdown | json
55
+ -o, --output <path> Write report to file
56
+ -w, --workers <n> Worker thread count (default: 4)
57
+ -i, --interactive Launch interactive TUI to browse results
58
+ -b, --batch <n> Limit output to the first N issues (default: 10)
59
+ --parallel Enable parallel execution (default)
60
+ --no-parallel Run sequentially
61
+ --verbose Debug output
62
+ -q, --quiet Exit code only, no output
63
+ -h, --help Show help
64
+ -v, --version Show version
65
+ ```
66
+
67
+ ## Interactive Mode
68
+
69
+ Launch with `ras -i` to get a keyboard-driven TUI for browsing results, organized by code smell:
70
+
71
+ ```
72
+ React Anti-Pattern Sniffer │ 12/42 issues shown • 85 files scanned
73
+
74
+ ▸ ▼ Prop Explosion (6 issues)
75
+ ● src/components/UserProfile.tsx:15 UserProfile — has 10 props
76
+ ● src/components/Dashboard.tsx:42 Dashboard — has 8 props
77
+ ● src/pages/Settings.tsx:23 SettingsForm — has 9 props
78
+
79
+ ▶ God Hook (3 issues)
80
+
81
+ ▶ Prop Drilling (3 issues)
82
+
83
+ [c]opy as prompt [a]ll as md [x] ignore [f]ilter [d]etails [q]uit
84
+ ↑/↓ navigate ←/→ or enter collapse/expand tab next group
85
+ ```
86
+
87
+ ### Keyboard Controls
88
+
89
+ | Key | Action |
90
+ |-----|--------|
91
+ | `↑`/`↓` or `j`/`k` | Navigate between items |
92
+ | `Enter`/`→`/`←` | Expand/collapse smell groups |
93
+ | `Tab` | Jump to next group |
94
+ | `d` | Toggle details (show suggestion + metadata) |
95
+ | `c` | Copy current issue as AI prompt (markdown to clipboard) |
96
+ | `a` | Copy all visible issues as markdown |
97
+ | `x` | Ignore component (adds to `.snifferignore`) |
98
+ | `f` | Cycle filter by smell type |
99
+ | `p` | Print current issue markdown (clipboard fallback) |
100
+ | `q` | Quit |
101
+
102
+ ## Batch Mode
103
+
104
+ Use `-b` to limit output to the first N issues. Works in both interactive and non-interactive mode:
105
+
106
+ ```bash
107
+ # Show first 5 issues as markdown
108
+ ras -b 5
109
+
110
+ # Browse first 20 issues in TUI
111
+ ras -i -b 20
112
+
113
+ # Only god-hook issues, first 3
114
+ ras -b 3 -s god-hook
115
+ ```
116
+
117
+ ## Sniffers
118
+
119
+ ### Prop Explosion
120
+
121
+ Detects components with too many props, suggesting decomposition.
122
+
123
+ ```jsx
124
+ // ⚠ Flagged: 10 props exceeds threshold of 7
125
+ const UserProfile = ({ firstName, lastName, email, phone, avatar, address, role, permissions, isActive, onUpdate }) => { ... }
126
+ ```
127
+
128
+ **Fix:** Group related props into objects, use Context, or split the component.
129
+
130
+ ### God Hook
131
+
132
+ Detects custom hooks with excessive state, effects, or responsibilities.
133
+
134
+ ```jsx
135
+ // ⚠ Flagged: 6 useState, 4 useEffect
136
+ function useUserDashboard(userId) {
137
+ const [user, setUser] = useState(null);
138
+ const [posts, setPosts] = useState([]);
139
+ // ...6 more useState, 4 useEffect
140
+ }
141
+ ```
142
+
143
+ **Fix:** Split into focused sub-hooks (`useUser`, `usePosts`, `useNotifications`).
144
+
145
+ ### Prop Drilling
146
+
147
+ Detects props that are received but only forwarded to children without local use.
148
+
149
+ ```jsx
150
+ // ⚠ Flagged: theme, locale passed through without use
151
+ const Wrapper = ({ theme, locale, children }) => (
152
+ <Header theme={theme} locale={locale} />
153
+ );
154
+ ```
155
+
156
+ **Fix:** Use React Context or component composition.
157
+
158
+ ## `.snifferignore`
159
+
160
+ Create a `.snifferignore` file in your project root to persistently skip specific components or files. This file is automatically updated when you press `x` in interactive mode.
161
+
162
+ ```gitignore
163
+ # Ignore a specific component for a specific sniffer
164
+ src/components/UserProfile.tsx:UserProfile # prop-explosion
165
+
166
+ # Ignore all issues in a file
167
+ src/legacy/OldDashboard.tsx
168
+
169
+ # Ignore a hook
170
+ src/hooks/useMonolith.ts:useMonolith # god-hook
171
+ ```
172
+
173
+ Format: `<file-path>:<ComponentName> # <sniffer-name>`
174
+
175
+ - `ComponentName` and `# sniffer-name` are optional
176
+ - Lines starting with `#` are comments
177
+
178
+ ## Configuration
179
+
180
+ Create `.snifferrc.json` in your project root:
181
+
182
+ ```json
183
+ {
184
+ "include": ["**/*.{jsx,tsx}"],
185
+ "exclude": ["node_modules", "dist", "build", "**/*.test.*"],
186
+ "parallel": true,
187
+ "maxWorkers": 4,
188
+ "timeoutMs": 30000,
189
+ "outputFormat": "markdown",
190
+ "sniffers": {
191
+ "prop-explosion": {
192
+ "enabled": true,
193
+ "threshold": 7,
194
+ "severity": "warning"
195
+ },
196
+ "god-hook": {
197
+ "enabled": true,
198
+ "maxUseState": 4,
199
+ "maxUseEffect": 3,
200
+ "maxTotalHooks": 10,
201
+ "severity": "warning"
202
+ },
203
+ "prop-drilling": {
204
+ "enabled": true,
205
+ "severity": "warning",
206
+ "whitelistedProps": ["className", "style", "children", "key", "ref", "id"]
207
+ }
208
+ },
209
+ "plugins": []
210
+ }
211
+ ```
212
+
213
+ ## Git Hooks (Husky)
214
+
215
+ Add to `.husky/pre-commit`:
216
+
217
+ ```bash
218
+ npx react-sniff --quiet
219
+ ```
220
+
221
+ Exit codes:
222
+ - `0` — no issues found
223
+ - `1` — anti-patterns detected
224
+ - `2` — configuration or runtime error
225
+
226
+ ## Custom Plugins
227
+
228
+ Create a sniffer module:
229
+
230
+ ```js
231
+ // my-sniffer.js
232
+ module.exports = {
233
+ name: 'no-inline-styles',
234
+ description: 'Detects inline style objects in JSX',
235
+ meta: {
236
+ name: 'no-inline-styles',
237
+ description: 'Flags inline style={{}} usage',
238
+ category: 'custom',
239
+ severity: 'info',
240
+ defaultConfig: {},
241
+ },
242
+ detect(fileContent, filePath, config) {
243
+ const detections = [];
244
+ // Your detection logic here
245
+ // Return array of Detection objects:
246
+ // { snifferName, filePath, line, column, message, severity, suggestion }
247
+ return detections;
248
+ },
249
+ };
250
+ ```
251
+
252
+ Register in `.snifferrc.json`:
253
+
254
+ ```json
255
+ {
256
+ "plugins": [
257
+ { "path": "./my-sniffer.js" }
258
+ ]
259
+ }
260
+ ```
261
+
262
+ ### Plugin Security
263
+
264
+ Plugins are validated before execution:
265
+ 1. **Schema check** — must export `name`, `description`, `meta`, `detect`
266
+ 2. **Security scan** — warns on `eval()`, `Function()`, `child_process`, etc.
267
+ 3. **Smoke test** — must return an array on empty input
268
+ 4. **Worker isolation** — runs in separate V8 isolate with memory limits
269
+ 5. **Timeout** — killed after `timeoutMs` (default 30s)
270
+
271
+ ## Programmatic API
272
+
273
+ ```js
274
+ const { orchestrate } = require('react-anti-pattern-sniffer');
275
+
276
+ const result = await orchestrate(config, targetDir);
277
+ console.log(result.output); // formatted report
278
+ console.log(result.issueCount); // number of issues
279
+ console.log(result.fileCount); // files scanned
280
+ console.log(result.grouped); // Map<string, SnifferResult[]>
281
+ ```
282
+
283
+ ## Requirements
284
+
285
+ - Node.js >= 20.0.0
286
+
287
+ ## License
288
+
289
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ require('../dist/src/cli/index.js');
@@ -0,0 +1,10 @@
1
+ export interface ParsedArgs {
2
+ flags: Record<string, string | boolean>;
3
+ positionals: string[];
4
+ }
5
+ /**
6
+ * Parse CLI arguments with zero dependencies.
7
+ * Supports: --key=value, --key value, --boolean, --no-x (negation), -short aliases
8
+ */
9
+ export declare function parseArgs(argv: string[]): ParsedArgs;
10
+ //# sourceMappingURL=arg-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arg-parser.d.ts","sourceRoot":"","sources":["../../../src/cli/arg-parser.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IACxC,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAgBD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAyDpD"}
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseArgs = parseArgs;
4
+ const SHORT_ALIASES = {
5
+ '-c': '--config',
6
+ '-d': '--dir',
7
+ '-f': '--format',
8
+ '-o': '--output',
9
+ '-s': '--sniffers',
10
+ '-w': '--workers',
11
+ '-h': '--help',
12
+ '-v': '--version',
13
+ '-q': '--quiet',
14
+ '-i': '--interactive',
15
+ '-b': '--batch',
16
+ };
17
+ /**
18
+ * Parse CLI arguments with zero dependencies.
19
+ * Supports: --key=value, --key value, --boolean, --no-x (negation), -short aliases
20
+ */
21
+ function parseArgs(argv) {
22
+ const flags = {};
23
+ const positionals = [];
24
+ let i = 0;
25
+ while (i < argv.length) {
26
+ let token = argv[i];
27
+ // Expand short aliases
28
+ if (SHORT_ALIASES[token]) {
29
+ token = SHORT_ALIASES[token];
30
+ }
31
+ if (token.startsWith('--no-')) {
32
+ // Negation: --no-parallel => parallel = false
33
+ const key = token.substring(5);
34
+ flags[key] = false;
35
+ i++;
36
+ }
37
+ else if (token.startsWith('--')) {
38
+ const eqIndex = token.indexOf('=');
39
+ if (eqIndex !== -1) {
40
+ // --key=value
41
+ const key = token.substring(2, eqIndex);
42
+ const value = token.substring(eqIndex + 1);
43
+ flags[key] = value;
44
+ i++;
45
+ }
46
+ else {
47
+ const key = token.substring(2);
48
+ const next = argv[i + 1];
49
+ // If next token exists and doesn't start with -, it's the value
50
+ if (next !== undefined && !next.startsWith('-')) {
51
+ flags[key] = next;
52
+ i += 2;
53
+ }
54
+ else {
55
+ // Boolean flag
56
+ flags[key] = true;
57
+ i++;
58
+ }
59
+ }
60
+ }
61
+ else if (token.startsWith('-') && token.length === 2) {
62
+ // Single-char flag not in aliases
63
+ const key = token.substring(1);
64
+ const next = argv[i + 1];
65
+ if (next !== undefined && !next.startsWith('-')) {
66
+ flags[key] = next;
67
+ i += 2;
68
+ }
69
+ else {
70
+ flags[key] = true;
71
+ i++;
72
+ }
73
+ }
74
+ else {
75
+ positionals.push(token);
76
+ i++;
77
+ }
78
+ }
79
+ return { flags, positionals };
80
+ }
81
+ //# sourceMappingURL=arg-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arg-parser.js","sourceRoot":"","sources":["../../../src/cli/arg-parser.ts"],"names":[],"mappings":";;AAuBA,8BAyDC;AA3ED,MAAM,aAAa,GAA2B;IAC5C,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,OAAO;IACb,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,eAAe;IACrB,IAAI,EAAE,SAAS;CAChB,CAAC;AAEF;;;GAGG;AACH,SAAgB,SAAS,CAAC,IAAc;IACtC,MAAM,KAAK,GAAqC,EAAE,CAAC;IACnD,MAAM,WAAW,GAAa,EAAE,CAAC;IAEjC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,IAAI,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAEpB,uBAAuB;QACvB,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QAED,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,8CAA8C;YAC9C,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAC/B,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACnB,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,OAAO,KAAK,CAAC,CAAC,EAAE,CAAC;gBACnB,cAAc;gBACd,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;gBACxC,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;gBAC3C,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;gBACnB,CAAC,EAAE,CAAC;YACN,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;gBAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBACzB,gEAAgE;gBAChE,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;oBAChD,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;oBAClB,CAAC,IAAI,CAAC,CAAC;gBACT,CAAC;qBAAM,CAAC;oBACN,eAAe;oBACf,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;oBAClB,CAAC,EAAE,CAAC;gBACN,CAAC;YACH,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvD,kCAAkC;YAClC,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACzB,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChD,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;gBAClB,CAAC,IAAI,CAAC,CAAC;YACT,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;gBAClB,CAAC,EAAE,CAAC;YACN,CAAC;QACH,CAAC;aAAM,CAAC;YACN,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC,EAAE,CAAC;QACN,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;AAChC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { SnifferConfig } from '../sniffers/sniffer-interface.js';
2
+ declare const DEFAULT_CONFIG: SnifferConfig;
3
+ /**
4
+ * Load configuration by merging:
5
+ * 1. Built-in defaults
6
+ * 2. Config file (.snifferrc.json or --config path)
7
+ * 3. CLI flags
8
+ */
9
+ export declare function loadConfig(flags: Record<string, string | boolean>): SnifferConfig;
10
+ export { DEFAULT_CONFIG };
11
+ //# sourceMappingURL=config-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../../src/cli/config-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,QAAA,MAAM,cAAc,EAAE,aAkBrB,CAAC;AAoDF;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,aAAa,CA6EjF;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CONFIG = void 0;
4
+ exports.loadConfig = loadConfig;
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
7
+ const DEFAULT_CONFIG = {
8
+ include: ['**/*.{jsx,tsx}'],
9
+ exclude: ['node_modules', 'dist', 'build', '**/*.test.*', '**/*.spec.*'],
10
+ parallel: true,
11
+ maxWorkers: 4,
12
+ timeoutMs: 30000,
13
+ outputFormat: 'markdown',
14
+ outputPath: null,
15
+ sniffers: {
16
+ 'prop-explosion': { enabled: true, threshold: 7, severity: 'warning' },
17
+ 'god-hook': { enabled: true, maxUseState: 4, maxUseEffect: 3, maxTotalHooks: 10, severity: 'warning' },
18
+ 'prop-drilling': {
19
+ enabled: true,
20
+ severity: 'warning',
21
+ whitelistedProps: ['className', 'style', 'children', 'key', 'ref', 'id', 'data-testid'],
22
+ },
23
+ },
24
+ plugins: [],
25
+ };
26
+ exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
27
+ /**
28
+ * Deep merge two objects. Source values override target values.
29
+ * Arrays are replaced, not merged.
30
+ */
31
+ function deepMerge(target, source) {
32
+ const result = { ...target };
33
+ for (const key of Object.keys(source)) {
34
+ const sourceVal = source[key];
35
+ const targetVal = target[key];
36
+ if (sourceVal !== null &&
37
+ typeof sourceVal === 'object' &&
38
+ !Array.isArray(sourceVal) &&
39
+ targetVal !== null &&
40
+ typeof targetVal === 'object' &&
41
+ !Array.isArray(targetVal)) {
42
+ result[key] = deepMerge(targetVal, sourceVal);
43
+ }
44
+ else {
45
+ result[key] = sourceVal;
46
+ }
47
+ }
48
+ return result;
49
+ }
50
+ /**
51
+ * Find a config file by searching common names in the given directory.
52
+ */
53
+ function findConfigFile(dir) {
54
+ const candidates = [
55
+ '.snifferrc.json',
56
+ '.snifferrc.js',
57
+ 'sniffer.config.json',
58
+ 'sniffer.config.js',
59
+ ];
60
+ for (const name of candidates) {
61
+ const fullPath = (0, node_path_1.join)(dir, name);
62
+ if ((0, node_fs_1.existsSync)(fullPath))
63
+ return fullPath;
64
+ }
65
+ return null;
66
+ }
67
+ /**
68
+ * Load configuration by merging:
69
+ * 1. Built-in defaults
70
+ * 2. Config file (.snifferrc.json or --config path)
71
+ * 3. CLI flags
72
+ */
73
+ function loadConfig(flags) {
74
+ let fileConfig = {};
75
+ // Determine config file path
76
+ const configPath = typeof flags.config === 'string'
77
+ ? (0, node_path_1.resolve)(flags.config)
78
+ : findConfigFile(process.cwd());
79
+ if (configPath) {
80
+ if (!(0, node_fs_1.existsSync)(configPath)) {
81
+ if (flags.config) {
82
+ throw new Error(`Config file not found: ${configPath}`);
83
+ }
84
+ }
85
+ else {
86
+ try {
87
+ const raw = (0, node_fs_1.readFileSync)(configPath, 'utf8');
88
+ fileConfig = JSON.parse(raw);
89
+ }
90
+ catch (e) {
91
+ throw new Error(`Failed to parse config file ${configPath}: ${e instanceof Error ? e.message : String(e)}`);
92
+ }
93
+ }
94
+ }
95
+ // Merge defaults + file config
96
+ const merged = deepMerge(DEFAULT_CONFIG, fileConfig);
97
+ // Apply CLI flag overrides
98
+ if (typeof flags.dir === 'string') {
99
+ // Dir is handled at orchestrator level, not in config
100
+ }
101
+ if (typeof flags.format === 'string') {
102
+ if (flags.format !== 'markdown' && flags.format !== 'json') {
103
+ throw new Error(`Invalid format "${flags.format}". Supported: markdown, json`);
104
+ }
105
+ merged.outputFormat = flags.format;
106
+ }
107
+ if (typeof flags.output === 'string') {
108
+ merged.outputPath = flags.output;
109
+ }
110
+ if (typeof flags.workers === 'string') {
111
+ const n = parseInt(flags.workers, 10);
112
+ if (isNaN(n) || n < 1) {
113
+ throw new Error(`Invalid workers count "${flags.workers}". Must be a positive integer.`);
114
+ }
115
+ merged.maxWorkers = n;
116
+ }
117
+ if (flags.parallel === false || flags.parallel === 'false') {
118
+ merged.parallel = false;
119
+ }
120
+ else if (flags.parallel === true || flags.parallel === 'true') {
121
+ merged.parallel = true;
122
+ }
123
+ if (typeof flags.sniffers === 'string') {
124
+ const requested = flags.sniffers.split(',').map(s => s.trim());
125
+ // Disable all, then enable only requested
126
+ for (const key of Object.keys(merged.sniffers)) {
127
+ merged.sniffers[key].enabled = false;
128
+ }
129
+ for (const name of requested) {
130
+ if (merged.sniffers[name]) {
131
+ merged.sniffers[name].enabled = true;
132
+ }
133
+ else {
134
+ throw new Error(`Unknown sniffer "${name}". Available: ${Object.keys(merged.sniffers).join(', ')}`);
135
+ }
136
+ }
137
+ }
138
+ return merged;
139
+ }
140
+ //# sourceMappingURL=config-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../../src/cli/config-loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function printHelp(): void;
2
+ export declare function printVersion(): void;
3
+ //# sourceMappingURL=help.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../../src/cli/help.ts"],"names":[],"mappings":"AAGA,wBAAgB,SAAS,IAAI,IAAI,CA0ChC;AAED,wBAAgB,YAAY,IAAI,IAAI,CAQnC"}
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printHelp = printHelp;
4
+ exports.printVersion = printVersion;
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
7
+ function printHelp() {
8
+ const help = `
9
+ Usage: react-sniff [options] [dir]
10
+
11
+ Detect React anti-patterns in your codebase.
12
+
13
+ Options:
14
+ -d, --dir <path> Target directory to scan (default: cwd)
15
+ -c, --config <path> Path to config file (default: .snifferrc.json)
16
+ -s, --sniffers <list> Comma-separated list of sniffers to run
17
+ -f, --format <type> Output format: markdown | json (default: markdown)
18
+ -o, --output <path> Write report to file instead of stdout
19
+ -w, --workers <n> Number of worker threads (default: 4)
20
+ -i, --interactive Launch interactive TUI to browse results
21
+ -b, --batch <n> Show first N issues in interactive mode (default: 10)
22
+ --parallel Enable parallel execution (default: true)
23
+ --no-parallel Disable parallel execution
24
+ --verbose Show debug output
25
+ -q, --quiet Suppress all output (exit code only)
26
+ -h, --help Show this help message
27
+ -v, --version Show version number
28
+
29
+ Examples:
30
+ react-sniff Scan current directory
31
+ react-sniff src/ Scan specific directory
32
+ ras -i Interactive mode
33
+ ras -i -b 20 Interactive, show first 20 issues
34
+ react-sniff --sniffers prop-explosion Run only prop explosion sniffer
35
+ react-sniff --format json -o report.json Output JSON report to file
36
+ ras --no-parallel --verbose Run sequentially with debug output
37
+
38
+ Sniffers:
39
+ prop-explosion Detects components with too many props
40
+ god-hook Detects custom hooks that do too much
41
+ prop-drilling Detects props passed through without being used
42
+
43
+ Configuration:
44
+ Create a .snifferrc.json in your project root to customize thresholds.
45
+ See https://github.com/your-repo/react-anti-pattern-sniffer for details.
46
+ `.trim();
47
+ console.log(help);
48
+ }
49
+ function printVersion() {
50
+ try {
51
+ const pkgPath = (0, node_path_1.join)(__dirname, '..', '..', '..', 'package.json');
52
+ const pkg = JSON.parse((0, node_fs_1.readFileSync)(pkgPath, 'utf8'));
53
+ console.log(`react-anti-pattern-sniffer v${pkg.version}`);
54
+ }
55
+ catch {
56
+ console.log('react-anti-pattern-sniffer (unknown version)');
57
+ }
58
+ }
59
+ //# sourceMappingURL=help.js.map