roto-rooter 0.0.1 → 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 (55) hide show
  1. package/README.md +24 -17
  2. package/dist/analyzer.d.ts +1 -1
  3. package/dist/analyzer.d.ts.map +1 -1
  4. package/dist/analyzer.js +24 -26
  5. package/dist/analyzer.js.map +1 -1
  6. package/dist/checks/form-check.d.ts +1 -1
  7. package/dist/checks/form-check.d.ts.map +1 -1
  8. package/dist/checks/form-check.js +79 -17
  9. package/dist/checks/form-check.js.map +1 -1
  10. package/dist/checks/hydration-check.d.ts +12 -0
  11. package/dist/checks/hydration-check.d.ts.map +1 -0
  12. package/dist/checks/hydration-check.js +80 -0
  13. package/dist/checks/hydration-check.js.map +1 -0
  14. package/dist/checks/link-check.d.ts +1 -1
  15. package/dist/checks/link-check.js +10 -10
  16. package/dist/checks/loader-check.d.ts +1 -1
  17. package/dist/checks/loader-check.d.ts.map +1 -1
  18. package/dist/checks/loader-check.js +12 -12
  19. package/dist/checks/loader-check.js.map +1 -1
  20. package/dist/checks/params-check.d.ts +1 -1
  21. package/dist/checks/params-check.js +6 -6
  22. package/dist/cli.js +24 -24
  23. package/dist/index.d.ts +9 -9
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +8 -8
  26. package/dist/index.js.map +1 -1
  27. package/dist/parsers/action-parser.d.ts +2 -0
  28. package/dist/parsers/action-parser.d.ts.map +1 -1
  29. package/dist/parsers/action-parser.js +91 -8
  30. package/dist/parsers/action-parser.js.map +1 -1
  31. package/dist/parsers/component-parser.d.ts +1 -1
  32. package/dist/parsers/component-parser.d.ts.map +1 -1
  33. package/dist/parsers/component-parser.js +241 -41
  34. package/dist/parsers/component-parser.js.map +1 -1
  35. package/dist/parsers/route-parser.d.ts +1 -1
  36. package/dist/parsers/route-parser.js +19 -19
  37. package/dist/parsers/route-parser.js.map +1 -1
  38. package/dist/types.d.ts +23 -6
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/utils/ast-utils.d.ts +1 -1
  41. package/dist/utils/ast-utils.d.ts.map +1 -1
  42. package/dist/utils/ast-utils.js +10 -6
  43. package/dist/utils/ast-utils.js.map +1 -1
  44. package/dist/utils/suggestion.d.ts.map +1 -1
  45. package/dist/utils/suggestion.js +1 -1
  46. package/dist/utils/suggestion.js.map +1 -1
  47. package/package.json +23 -2
  48. package/dist/checks/a11y-check.d.ts +0 -6
  49. package/dist/checks/a11y-check.d.ts.map +0 -1
  50. package/dist/checks/a11y-check.js +0 -13
  51. package/dist/checks/a11y-check.js.map +0 -1
  52. package/dist/checks/interactive-check.d.ts +0 -6
  53. package/dist/checks/interactive-check.d.ts.map +0 -1
  54. package/dist/checks/interactive-check.js +0 -12
  55. package/dist/checks/interactive-check.js.map +0 -1
package/README.md CHANGED
@@ -1,52 +1,59 @@
1
- # react-router-analyzer
1
+ # roto-rooter
2
2
 
3
- Static analysis tool for React Router 7 applications that catches runtime errors at build time.
3
+ Static analysis and functional verifier tool for React Router applications.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install react-router-analyzer
8
+ npm install -g roto-rooter
9
9
  ```
10
10
 
11
11
  ## Usage
12
12
 
13
13
  ```bash
14
14
  # Check all files
15
- npx react-router-analyzer
15
+ rr
16
16
 
17
17
  # Check specific file(s)
18
- npx react-router-analyzer app/routes/employees.tsx
18
+ rr app/routes/employees.tsx
19
19
 
20
20
  # Run specific checks only
21
- npx react-router-analyzer --check links,forms
21
+ rr --check links,forms
22
22
 
23
23
  # Output as JSON
24
- npx react-router-analyzer --format json
24
+ rr --format json
25
25
 
26
- # Set root directory
27
- npx react-router-analyzer --root ./my-app
26
+ # Set app directory
27
+ rr --app ./my-app
28
28
  ```
29
29
 
30
30
  ## Checks
31
31
 
32
32
  - **links**: Validates Link, redirect(), and navigate() targets exist as defined routes
33
- - **forms**: Validates forms submit to routes with action exports
33
+ - **forms**: Validates forms submit to routes with action exports, and that form fields match what the action reads via formData.get()
34
34
  - **loader**: Validates useLoaderData() is only used in routes with loaders
35
35
  - **params**: Validates useParams() accesses only params defined in the route
36
- - **interactive**: Validates interactive elements have handlers (coming soon)
37
- - **a11y**: Accessibility checks requiring cross-element analysis (coming soon)
36
+ - **hydration**: Detects SSR hydration mismatch risks (dates, locale formatting, random values, browser APIs)
38
37
 
39
38
  ## Programmatic API
40
39
 
41
40
  ```typescript
42
- import { analyze } from "react-router-analyzer";
41
+ import { analyze } from 'roto-rooter';
43
42
 
44
43
  const result = analyze({
45
- root: "./my-app",
46
- files: [], // empty = all files
47
- checks: [], // empty = all checks
48
- format: "text",
44
+ root: './my-app',
45
+ files: [], // empty = all files
46
+ checks: [], // empty = all checks
47
+ format: 'text',
49
48
  });
50
49
 
51
50
  console.log(result.issues);
52
51
  ```
52
+
53
+ ## Development
54
+
55
+ ```bash
56
+ npm install # Install dependencies
57
+ npm test # Run tests
58
+ npm run build # Build for distribution
59
+ ```
@@ -1,4 +1,4 @@
1
- import type { AnalyzerResult, CliOptions } from "./types.js";
1
+ import type { AnalyzerResult, CliOptions } from './types.js';
2
2
  /**
3
3
  * Main analyzer - orchestrates parsing and checking
4
4
  */
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EAId,UAAU,EACX,MAAM,YAAY,CAAC;AAUpB;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,UAAU,GAAG,cAAc,CAqF3D"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EAId,UAAU,EACX,MAAM,YAAY,CAAC;AASpB;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,UAAU,GAAG,cAAc,CAmF3D"}
package/dist/analyzer.js CHANGED
@@ -1,13 +1,12 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { parseRoutes } from "./parsers/route-parser.js";
4
- import { parseComponent } from "./parsers/component-parser.js";
5
- import { checkLinks } from "./checks/link-check.js";
6
- import { checkForms } from "./checks/form-check.js";
7
- import { checkLoaders } from "./checks/loader-check.js";
8
- import { checkParams } from "./checks/params-check.js";
9
- import { checkInteractive } from "./checks/interactive-check.js";
10
- import { checkA11y } from "./checks/a11y-check.js";
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { parseRoutes } from './parsers/route-parser.js';
4
+ import { parseComponent } from './parsers/component-parser.js';
5
+ import { checkLinks } from './checks/link-check.js';
6
+ import { checkForms } from './checks/form-check.js';
7
+ import { checkLoaders } from './checks/loader-check.js';
8
+ import { checkParams } from './checks/params-check.js';
9
+ import { checkHydration } from './checks/hydration-check.js';
11
10
  /**
12
11
  * Main analyzer - orchestrates parsing and checking
13
12
  */
@@ -23,13 +22,13 @@ export function analyze(options) {
23
22
  return {
24
23
  issues: [
25
24
  {
26
- category: "links",
27
- severity: "error",
25
+ category: 'links',
26
+ severity: 'error',
28
27
  message: error instanceof Error
29
28
  ? error.message
30
- : "Failed to parse routes file",
29
+ : 'Failed to parse routes file',
31
30
  location: {
32
- file: path.join(root, "app", "routes.ts"),
31
+ file: path.join(root, 'app', 'routes.ts'),
33
32
  line: 1,
34
33
  column: 1,
35
34
  },
@@ -55,24 +54,23 @@ export function analyze(options) {
55
54
  }
56
55
  // Run checks
57
56
  const issues = [];
58
- const enabledChecks = new Set(checks.length > 0 ? checks : ["links", "forms", "loader", "params", "interactive", "a11y"]);
59
- if (enabledChecks.has("links")) {
57
+ const enabledChecks = new Set(checks.length > 0
58
+ ? checks
59
+ : ['links', 'forms', 'loader', 'params', 'hydration']);
60
+ if (enabledChecks.has('links')) {
60
61
  issues.push(...checkLinks(components, routes));
61
62
  }
62
- if (enabledChecks.has("forms")) {
63
+ if (enabledChecks.has('forms')) {
63
64
  issues.push(...checkForms(components, routes, root));
64
65
  }
65
- if (enabledChecks.has("loader")) {
66
+ if (enabledChecks.has('loader')) {
66
67
  issues.push(...checkLoaders(components));
67
68
  }
68
- if (enabledChecks.has("params")) {
69
+ if (enabledChecks.has('params')) {
69
70
  issues.push(...checkParams(components, routes, root));
70
71
  }
71
- if (enabledChecks.has("interactive")) {
72
- issues.push(...checkInteractive(components));
73
- }
74
- if (enabledChecks.has("a11y")) {
75
- issues.push(...checkA11y(components));
72
+ if (enabledChecks.has('hydration')) {
73
+ issues.push(...checkHydration(components));
76
74
  }
77
75
  // If specific files were provided, filter issues to only those files
78
76
  if (files.length > 0) {
@@ -91,13 +89,13 @@ function findComponentFiles(root, specificFiles) {
91
89
  return specificFiles.map((f) => path.resolve(f));
92
90
  }
93
91
  // Find all TSX files in app/routes
94
- const routesDir = path.join(root, "app", "routes");
92
+ const routesDir = path.join(root, 'app', 'routes');
95
93
  if (!fs.existsSync(routesDir)) {
96
94
  return [];
97
95
  }
98
96
  const files = [];
99
97
  walkDir(routesDir, (filePath) => {
100
- if (filePath.endsWith(".tsx")) {
98
+ if (filePath.endsWith('.tsx')) {
101
99
  files.push(filePath);
102
100
  }
103
101
  });
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { AnalyzerIssue, ComponentAnalysis, RouteDefinition } from "../types.js";
1
+ import type { AnalyzerIssue, ComponentAnalysis, RouteDefinition } from '../types.js';
2
2
  /**
3
3
  * Check form-action wiring
4
4
  */
@@ -1 +1 @@
1
- {"version":3,"file":"form-check.d.ts","sourceRoot":"","sources":["../../src/checks/form-check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,eAAe,EAChB,MAAM,aAAa,CAAC;AAIrB;;GAEG;AACH,wBAAgB,UAAU,CACxB,UAAU,EAAE,iBAAiB,EAAE,EAC/B,MAAM,EAAE,eAAe,EAAE,EACzB,OAAO,EAAE,MAAM,GACd,aAAa,EAAE,CAWjB"}
1
+ {"version":3,"file":"form-check.d.ts","sourceRoot":"","sources":["../../src/checks/form-check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,eAAe,EAChB,MAAM,aAAa,CAAC;AAOrB;;GAEG;AACH,wBAAgB,UAAU,CACxB,UAAU,EAAE,iBAAiB,EAAE,EAC/B,MAAM,EAAE,eAAe,EAAE,EACzB,OAAO,EAAE,MAAM,GACd,aAAa,EAAE,CAWjB"}
@@ -1,6 +1,6 @@
1
- import * as path from "path";
2
- import { matchRoute } from "../parsers/route-parser.js";
3
- import { parseRouteExports } from "../parsers/action-parser.js";
1
+ import * as path from 'path';
2
+ import { matchRoute } from '../parsers/route-parser.js';
3
+ import { parseRouteExports, } from '../parsers/action-parser.js';
4
4
  /**
5
5
  * Check form-action wiring
6
6
  */
@@ -24,8 +24,8 @@ function validateForm(form, component, routes, rootDir) {
24
24
  const targetRoute = matchRoute(form.action, routes);
25
25
  if (!targetRoute) {
26
26
  issues.push({
27
- category: "forms",
28
- severity: "error",
27
+ category: 'forms',
28
+ severity: 'error',
29
29
  message: `Form action targets non-existent route: ${form.action}`,
30
30
  location: form.location,
31
31
  code: `<Form action="${form.action}">`,
@@ -33,19 +33,24 @@ function validateForm(form, component, routes, rootDir) {
33
33
  }
34
34
  else {
35
35
  // Check if the target route file has an action export
36
- const routeFilePath = path.join(rootDir, "app", targetRoute.file);
36
+ const routeFilePath = path.join(rootDir, 'app', targetRoute.file);
37
37
  try {
38
38
  const exports = parseRouteExports(routeFilePath);
39
39
  if (!exports.hasAction) {
40
40
  issues.push({
41
- category: "forms",
42
- severity: "error",
41
+ category: 'forms',
42
+ severity: 'error',
43
43
  message: `Form action targets route without action export`,
44
44
  location: form.location,
45
45
  code: `<Form action="${form.action}">`,
46
46
  suggestion: `Add an action export to ${targetRoute.file}`,
47
47
  });
48
48
  }
49
+ else {
50
+ // Action exists - check field alignment
51
+ const fieldIssues = validateFormFields(form, exports, targetRoute.file);
52
+ issues.push(...fieldIssues);
53
+ }
49
54
  }
50
55
  catch {
51
56
  // File doesn't exist or can't be parsed - route-parser should catch this
@@ -56,19 +61,76 @@ function validateForm(form, component, routes, rootDir) {
56
61
  // Form submits to current route - check if current file has action
57
62
  if (!component.hasAction) {
58
63
  issues.push({
59
- category: "forms",
60
- severity: "error",
61
- message: "Form in route with no action export",
64
+ category: 'forms',
65
+ severity: 'error',
66
+ message: 'Form in route with no action export',
67
+ location: form.location,
68
+ code: '<Form>',
69
+ suggestion: 'Add an action export to handle form submission',
70
+ });
71
+ }
72
+ else {
73
+ // Action exists in current file - check field alignment
74
+ const routeFilePath = component.file;
75
+ try {
76
+ const exports = parseRouteExports(routeFilePath);
77
+ if (exports.actionFields) {
78
+ const fieldIssues = validateFormFields(form, exports);
79
+ issues.push(...fieldIssues);
80
+ }
81
+ }
82
+ catch {
83
+ // Parsing failed - skip field validation
84
+ }
85
+ }
86
+ }
87
+ return issues;
88
+ }
89
+ /**
90
+ * Validate that form fields align with what the action expects
91
+ */
92
+ function validateFormFields(form, exports, targetFile) {
93
+ const issues = [];
94
+ const formFields = new Set(form.inputNames);
95
+ const actionFields = new Set(exports.actionFields ?? []);
96
+ // Skip validation if action doesn't read any fields (might use Object.fromEntries or similar)
97
+ if (actionFields.size === 0) {
98
+ return issues;
99
+ }
100
+ // Check for fields the action expects but form doesn't provide
101
+ for (const actionField of actionFields) {
102
+ if (!formFields.has(actionField)) {
103
+ const formCode = form.action
104
+ ? `<Form action="${form.action}">`
105
+ : '<Form>';
106
+ issues.push({
107
+ category: 'forms',
108
+ severity: 'error',
109
+ message: `Action reads field '${actionField}' but form has no input with name="${actionField}"`,
110
+ location: form.location,
111
+ code: formCode,
112
+ suggestion: targetFile
113
+ ? `Add <input name="${actionField}" /> to the form, or check the action in ${targetFile}`
114
+ : `Add <input name="${actionField}" /> to the form`,
115
+ });
116
+ }
117
+ }
118
+ // Check for form fields the action never reads (warning - might be intentional)
119
+ for (const formField of formFields) {
120
+ if (!actionFields.has(formField)) {
121
+ const formCode = form.action
122
+ ? `<Form action="${form.action}">`
123
+ : '<Form>';
124
+ issues.push({
125
+ category: 'forms',
126
+ severity: 'warning',
127
+ message: `Form field '${formField}' is never read by the action`,
62
128
  location: form.location,
63
- code: "<Form>",
64
- suggestion: "Add an action export to handle form submission",
129
+ code: formCode,
130
+ suggestion: `Remove unused input or add formData.get('${formField}') to the action`,
65
131
  });
66
132
  }
67
133
  }
68
- // Check for inputs without name attribute
69
- // Note: We track inputNames, but we should also warn about inputs WITHOUT names
70
- // This would require more sophisticated tracking in the component parser
71
- // For now, we'll skip this check
72
134
  return issues;
73
135
  }
74
136
  //# sourceMappingURL=form-check.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"form-check.js","sourceRoot":"","sources":["../../src/checks/form-check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAM7B,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAEhE;;GAEG;AACH,MAAM,UAAU,UAAU,CACxB,UAA+B,EAC/B,MAAyB,EACzB,OAAe;IAEf,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;YACnC,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;YAClE,MAAM,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,YAAY,CACnB,IAAmC,EACnC,SAA4B,EAC5B,MAAyB,EACzB,OAAe;IAEf,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,yEAAyE;IACzE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAEpD,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,CAAC,IAAI,CAAC;gBACV,QAAQ,EAAE,OAAO;gBACjB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,2CAA2C,IAAI,CAAC,MAAM,EAAE;gBACjE,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,iBAAiB,IAAI,CAAC,MAAM,IAAI;aACvC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,sDAAsD;YACtD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;YAClE,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,iBAAiB,CAAC,aAAa,CAAC,CAAC;gBACjD,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;oBACvB,MAAM,CAAC,IAAI,CAAC;wBACV,QAAQ,EAAE,OAAO;wBACjB,QAAQ,EAAE,OAAO;wBACjB,OAAO,EAAE,iDAAiD;wBAC1D,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,IAAI,EAAE,iBAAiB,IAAI,CAAC,MAAM,IAAI;wBACtC,UAAU,EAAE,2BAA2B,WAAW,CAAC,IAAI,EAAE;qBAC1D,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,yEAAyE;YAC3E,CAAC;QACH,CAAC;IACH,CAAC;SAAM,CAAC;QACN,mEAAmE;QACnE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC;gBACV,QAAQ,EAAE,OAAO;gBACjB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,qCAAqC;gBAC9C,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE,gDAAgD;aAC7D,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,0CAA0C;IAC1C,gFAAgF;IAChF,yEAAyE;IACzE,iCAAiC;IAEjC,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"form-check.js","sourceRoot":"","sources":["../../src/checks/form-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { AnalyzerIssue, ComponentAnalysis } from '../types.js';
2
+ /**
3
+ * Check for SSR hydration mismatch risks
4
+ *
5
+ * Detects patterns that cause hydration mismatches between server and client:
6
+ * - Date/time operations without consistent timezone handling
7
+ * - Locale-dependent formatting without explicit timezone
8
+ * - Random value generation during render
9
+ * - Browser-only API access outside useEffect
10
+ */
11
+ export declare function checkHydration(components: ComponentAnalysis[]): AnalyzerIssue[];
12
+ //# sourceMappingURL=hydration-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hydration-check.d.ts","sourceRoot":"","sources":["../../src/checks/hydration-check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EAElB,MAAM,aAAa,CAAC;AAErB;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,iBAAiB,EAAE,GAC9B,aAAa,EAAE,CAkBjB"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Check for SSR hydration mismatch risks
3
+ *
4
+ * Detects patterns that cause hydration mismatches between server and client:
5
+ * - Date/time operations without consistent timezone handling
6
+ * - Locale-dependent formatting without explicit timezone
7
+ * - Random value generation during render
8
+ * - Browser-only API access outside useEffect
9
+ */
10
+ export function checkHydration(components) {
11
+ const issues = [];
12
+ for (const component of components) {
13
+ for (const risk of component.hydrationRisks) {
14
+ // Skip risks that are already mitigated
15
+ if (risk.inUseEffect || risk.hasSuppressWarning) {
16
+ continue;
17
+ }
18
+ const issue = createIssueForRisk(risk);
19
+ if (issue) {
20
+ issues.push(issue);
21
+ }
22
+ }
23
+ }
24
+ return issues;
25
+ }
26
+ /**
27
+ * Create an analyzer issue for a hydration risk
28
+ */
29
+ function createIssueForRisk(risk) {
30
+ switch (risk.type) {
31
+ case 'date-render':
32
+ return {
33
+ category: 'hydration',
34
+ severity: 'error',
35
+ message: 'Date created during render causes hydration mismatch',
36
+ location: risk.location,
37
+ code: risk.code,
38
+ suggestion: 'Move to useEffect, use suppressHydrationWarning, or pass date from loader',
39
+ };
40
+ case 'locale-format':
41
+ return {
42
+ category: 'hydration',
43
+ severity: 'error',
44
+ message: 'Locale-dependent formatting without explicit timeZone causes hydration mismatch',
45
+ location: risk.location,
46
+ code: risk.code,
47
+ suggestion: 'Add { timeZone: "UTC" } option, use suppressHydrationWarning, or format in useEffect',
48
+ };
49
+ case 'random-value':
50
+ return {
51
+ category: 'hydration',
52
+ severity: 'error',
53
+ message: 'Random value in render will cause hydration mismatch',
54
+ location: risk.location,
55
+ code: risk.code,
56
+ suggestion: 'Use React.useId() for IDs, or generate in useEffect and store in state',
57
+ };
58
+ case 'browser-api':
59
+ return {
60
+ category: 'hydration',
61
+ severity: 'error',
62
+ message: 'Browser-only API accessed during render will fail on server and cause hydration mismatch',
63
+ location: risk.location,
64
+ code: risk.code,
65
+ suggestion: 'Move to useEffect or check typeof window !== "undefined" first',
66
+ };
67
+ case 'loader-date':
68
+ return {
69
+ category: 'hydration',
70
+ severity: 'warning',
71
+ message: 'Date from loader may cause hydration mismatch when formatted',
72
+ location: risk.location,
73
+ code: risk.code,
74
+ suggestion: 'Format with explicit timeZone, use suppressHydrationWarning, or format in useEffect',
75
+ };
76
+ default:
77
+ return undefined;
78
+ }
79
+ }
80
+ //# sourceMappingURL=hydration-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hydration-check.js","sourceRoot":"","sources":["../../src/checks/hydration-check.ts"],"names":[],"mappings":"AAMA;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAC5B,UAA+B;IAE/B,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,cAAc,EAAE,CAAC;YAC5C,wCAAwC;YACxC,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAChD,SAAS;YACX,CAAC;YAED,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CAAC,IAAmB;IAC7C,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,aAAa;YAChB,OAAO;gBACL,QAAQ,EAAE,WAAW;gBACrB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,sDAAsD;gBAC/D,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,UAAU,EACR,2EAA2E;aAC9E,CAAC;QAEJ,KAAK,eAAe;YAClB,OAAO;gBACL,QAAQ,EAAE,WAAW;gBACrB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EACL,iFAAiF;gBACnF,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,UAAU,EACR,sFAAsF;aACzF,CAAC;QAEJ,KAAK,cAAc;YACjB,OAAO;gBACL,QAAQ,EAAE,WAAW;gBACrB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,sDAAsD;gBAC/D,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,UAAU,EACR,wEAAwE;aAC3E,CAAC;QAEJ,KAAK,aAAa;YAChB,OAAO;gBACL,QAAQ,EAAE,WAAW;gBACrB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EACL,0FAA0F;gBAC5F,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,UAAU,EACR,gEAAgE;aACnE,CAAC;QAEJ,KAAK,aAAa;YAChB,OAAO;gBACL,QAAQ,EAAE,WAAW;gBACrB,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,8DAA8D;gBACvE,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,UAAU,EACR,qFAAqF;aACxF,CAAC;QAEJ;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { AnalyzerIssue, ComponentAnalysis, RouteDefinition } from "../types.js";
1
+ import type { AnalyzerIssue, ComponentAnalysis, RouteDefinition } from '../types.js';
2
2
  /**
3
3
  * Check all links in components against defined routes
4
4
  */
@@ -1,5 +1,5 @@
1
- import { matchRoute, matchDynamicPattern, getAllRoutePaths, } from "../parsers/route-parser.js";
2
- import { findBestMatch, formatSuggestion } from "../utils/suggestion.js";
1
+ import { matchRoute, matchDynamicPattern, getAllRoutePaths, } from '../parsers/route-parser.js';
2
+ import { findBestMatch, formatSuggestion } from '../utils/suggestion.js';
3
3
  /**
4
4
  * Check all links in components against defined routes
5
5
  */
@@ -27,11 +27,11 @@ function validateLink(link, routes, allPaths) {
27
27
  if (!match) {
28
28
  const suggestion = findBestMatch(pattern, allPaths);
29
29
  return {
30
- category: "links",
31
- severity: "error",
32
- message: "No matching route for dynamic link pattern",
30
+ category: 'links',
31
+ severity: 'error',
32
+ message: 'No matching route for dynamic link pattern',
33
33
  location: link.location,
34
- code: `${link.type === "link" ? "href" : link.type}="${link.href}"`,
34
+ code: `${link.type === 'link' ? 'href' : link.type}="${link.href}"`,
35
35
  suggestion: formatSuggestion(suggestion),
36
36
  };
37
37
  }
@@ -42,11 +42,11 @@ function validateLink(link, routes, allPaths) {
42
42
  if (!match) {
43
43
  const suggestion = findBestMatch(link.href, allPaths);
44
44
  return {
45
- category: "links",
46
- severity: "error",
47
- message: "No matching route",
45
+ category: 'links',
46
+ severity: 'error',
47
+ message: 'No matching route',
48
48
  location: link.location,
49
- code: `${link.type === "link" ? "href" : link.type}="${link.href}"`,
49
+ code: `${link.type === 'link' ? 'href' : link.type}="${link.href}"`,
50
50
  suggestion: formatSuggestion(suggestion),
51
51
  };
52
52
  }
@@ -1,4 +1,4 @@
1
- import type { AnalyzerIssue, ComponentAnalysis } from "../types.js";
1
+ import type { AnalyzerIssue, ComponentAnalysis } from '../types.js';
2
2
  /**
3
3
  * Check loader-component binding
4
4
  */
@@ -1 +1 @@
1
- {"version":3,"file":"loader-check.d.ts","sourceRoot":"","sources":["../../src/checks/loader-check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEpE;;GAEG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,iBAAiB,EAAE,GAC9B,aAAa,EAAE,CASjB"}
1
+ {"version":3,"file":"loader-check.d.ts","sourceRoot":"","sources":["../../src/checks/loader-check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEpE;;GAEG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,iBAAiB,EAAE,GAAG,aAAa,EAAE,CAS7E"}
@@ -15,24 +15,24 @@ export function checkLoaders(components) {
15
15
  function validateLoaderUsage(component) {
16
16
  const issues = [];
17
17
  for (const hook of component.dataHooks) {
18
- if (hook.hook === "useLoaderData" && !component.hasLoader) {
18
+ if (hook.hook === 'useLoaderData' && !component.hasLoader) {
19
19
  issues.push({
20
- category: "loader",
21
- severity: "error",
22
- message: "useLoaderData() called but route has no loader",
20
+ category: 'loader',
21
+ severity: 'error',
22
+ message: 'useLoaderData() called but route has no loader',
23
23
  location: hook.location,
24
- code: "useLoaderData()",
25
- suggestion: "Add a loader function or remove the hook",
24
+ code: 'useLoaderData()',
25
+ suggestion: 'Add a loader function or remove the hook',
26
26
  });
27
27
  }
28
- if (hook.hook === "useActionData" && !component.hasAction) {
28
+ if (hook.hook === 'useActionData' && !component.hasAction) {
29
29
  issues.push({
30
- category: "loader",
31
- severity: "warning",
32
- message: "useActionData() called but route has no action",
30
+ category: 'loader',
31
+ severity: 'warning',
32
+ message: 'useActionData() called but route has no action',
33
33
  location: hook.location,
34
- code: "useActionData()",
35
- suggestion: "Add an action function or remove the hook",
34
+ code: 'useActionData()',
35
+ suggestion: 'Add an action function or remove the hook',
36
36
  });
37
37
  }
38
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"loader-check.js","sourceRoot":"","sources":["../../src/checks/loader-check.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,UAA+B;IAE/B,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,eAAe,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;QACvD,MAAM,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAAC,SAA4B;IACvD,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,SAAS,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC;YAC1D,MAAM,CAAC,IAAI,CAAC;gBACV,QAAQ,EAAE,QAAQ;gBAClB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,gDAAgD;gBACzD,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,iBAAiB;gBACvB,UAAU,EAAE,0CAA0C;aACvD,CAAC,CAAC;QACL,CAAC;QAED,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC;YAC1D,MAAM,CAAC,IAAI,CAAC;gBACV,QAAQ,EAAE,QAAQ;gBAClB,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,gDAAgD;gBACzD,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,iBAAiB;gBACvB,UAAU,EAAE,2CAA2C;aACxD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"loader-check.js","sourceRoot":"","sources":["../../src/checks/loader-check.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,UAA+B;IAC1D,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,eAAe,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;QACvD,MAAM,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAAC,SAA4B;IACvD,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,SAAS,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC;YAC1D,MAAM,CAAC,IAAI,CAAC;gBACV,QAAQ,EAAE,QAAQ;gBAClB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,gDAAgD;gBACzD,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,iBAAiB;gBACvB,UAAU,EAAE,0CAA0C;aACvD,CAAC,CAAC;QACL,CAAC;QAED,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC;YAC1D,MAAM,CAAC,IAAI,CAAC;gBACV,QAAQ,EAAE,QAAQ;gBAClB,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,gDAAgD;gBACzD,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,iBAAiB;gBACvB,UAAU,EAAE,2CAA2C;aACxD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { AnalyzerIssue, ComponentAnalysis, RouteDefinition } from "../types.js";
1
+ import type { AnalyzerIssue, ComponentAnalysis, RouteDefinition } from '../types.js';
2
2
  /**
3
3
  * Check route parameter consistency
4
4
  */
@@ -1,4 +1,4 @@
1
- import * as path from "path";
1
+ import * as path from 'path';
2
2
  /**
3
3
  * Check route parameter consistency
4
4
  */
@@ -8,7 +8,7 @@ export function checkParams(components, routes, rootDir) {
8
8
  const fileToRoute = new Map();
9
9
  function mapRoutes(routeList) {
10
10
  for (const route of routeList) {
11
- const fullPath = path.join(rootDir, "app", route.file);
11
+ const fullPath = path.join(rootDir, 'app', route.file);
12
12
  fileToRoute.set(fullPath, route);
13
13
  if (route.children) {
14
14
  mapRoutes(route.children);
@@ -34,17 +34,17 @@ function validateParamUsage(component, route) {
34
34
  const issues = [];
35
35
  const routeParams = new Set(route.params);
36
36
  for (const hook of component.dataHooks) {
37
- if (hook.hook === "useParams" && hook.accessedParams) {
37
+ if (hook.hook === 'useParams' && hook.accessedParams) {
38
38
  for (const param of hook.accessedParams) {
39
39
  if (!routeParams.has(param)) {
40
40
  issues.push({
41
- category: "params",
42
- severity: "error",
41
+ category: 'params',
42
+ severity: 'error',
43
43
  message: `useParams() accesses "${param}" but route has no :${param} parameter`,
44
44
  location: hook.location,
45
45
  code: `useParams().${param}`,
46
46
  suggestion: route.params.length > 0
47
- ? `Available params: ${route.params.map((p) => ":" + p).join(", ")}`
47
+ ? `Available params: ${route.params.map((p) => ':' + p).join(', ')}`
48
48
  : `Route ${route.path} has no parameters`,
49
49
  });
50
50
  }