roto-rooter 0.0.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/README.md +52 -0
- package/dist/analyzer.d.ts +6 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +121 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/checks/a11y-check.d.ts +6 -0
- package/dist/checks/a11y-check.d.ts.map +1 -0
- package/dist/checks/a11y-check.js +13 -0
- package/dist/checks/a11y-check.js.map +1 -0
- package/dist/checks/form-check.d.ts +6 -0
- package/dist/checks/form-check.d.ts.map +1 -0
- package/dist/checks/form-check.js +74 -0
- package/dist/checks/form-check.js.map +1 -0
- package/dist/checks/interactive-check.d.ts +6 -0
- package/dist/checks/interactive-check.d.ts.map +1 -0
- package/dist/checks/interactive-check.js +12 -0
- package/dist/checks/interactive-check.js.map +1 -0
- package/dist/checks/link-check.d.ts +6 -0
- package/dist/checks/link-check.d.ts.map +1 -0
- package/dist/checks/link-check.js +56 -0
- package/dist/checks/link-check.js.map +1 -0
- package/dist/checks/loader-check.d.ts +6 -0
- package/dist/checks/loader-check.d.ts.map +1 -0
- package/dist/checks/loader-check.js +41 -0
- package/dist/checks/loader-check.js.map +1 -0
- package/dist/checks/params-check.d.ts +6 -0
- package/dist/checks/params-check.d.ts.map +1 -0
- package/dist/checks/params-check.js +56 -0
- package/dist/checks/params-check.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +151 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/action-parser.d.ts +17 -0
- package/dist/parsers/action-parser.d.ts.map +1 -0
- package/dist/parsers/action-parser.js +53 -0
- package/dist/parsers/action-parser.js.map +1 -0
- package/dist/parsers/component-parser.d.ts +6 -0
- package/dist/parsers/component-parser.d.ts.map +1 -0
- package/dist/parsers/component-parser.js +354 -0
- package/dist/parsers/component-parser.js.map +1 -0
- package/dist/parsers/route-parser.d.ts +19 -0
- package/dist/parsers/route-parser.d.ts.map +1 -0
- package/dist/parsers/route-parser.js +275 -0
- package/dist/parsers/route-parser.js.map +1 -0
- package/dist/types.d.ts +123 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/ast-utils.d.ts +41 -0
- package/dist/utils/ast-utils.d.ts.map +1 -0
- package/dist/utils/ast-utils.js +114 -0
- package/dist/utils/ast-utils.js.map +1 -0
- package/dist/utils/suggestion.d.ts +10 -0
- package/dist/utils/suggestion.d.ts.map +1 -0
- package/dist/utils/suggestion.js +30 -0
- package/dist/utils/suggestion.js.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# react-router-analyzer
|
|
2
|
+
|
|
3
|
+
Static analysis tool for React Router 7 applications that catches runtime errors at build time.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install react-router-analyzer
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Check all files
|
|
15
|
+
npx react-router-analyzer
|
|
16
|
+
|
|
17
|
+
# Check specific file(s)
|
|
18
|
+
npx react-router-analyzer app/routes/employees.tsx
|
|
19
|
+
|
|
20
|
+
# Run specific checks only
|
|
21
|
+
npx react-router-analyzer --check links,forms
|
|
22
|
+
|
|
23
|
+
# Output as JSON
|
|
24
|
+
npx react-router-analyzer --format json
|
|
25
|
+
|
|
26
|
+
# Set root directory
|
|
27
|
+
npx react-router-analyzer --root ./my-app
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Checks
|
|
31
|
+
|
|
32
|
+
- **links**: Validates Link, redirect(), and navigate() targets exist as defined routes
|
|
33
|
+
- **forms**: Validates forms submit to routes with action exports
|
|
34
|
+
- **loader**: Validates useLoaderData() is only used in routes with loaders
|
|
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)
|
|
38
|
+
|
|
39
|
+
## Programmatic API
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { analyze } from "react-router-analyzer";
|
|
43
|
+
|
|
44
|
+
const result = analyze({
|
|
45
|
+
root: "./my-app",
|
|
46
|
+
files: [], // empty = all files
|
|
47
|
+
checks: [], // empty = all checks
|
|
48
|
+
format: "text",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
console.log(result.issues);
|
|
52
|
+
```
|
|
@@ -0,0 +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"}
|
package/dist/analyzer.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
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";
|
|
11
|
+
/**
|
|
12
|
+
* Main analyzer - orchestrates parsing and checking
|
|
13
|
+
*/
|
|
14
|
+
export function analyze(options) {
|
|
15
|
+
const { root, files, checks } = options;
|
|
16
|
+
// Parse routes (always global)
|
|
17
|
+
let routes = [];
|
|
18
|
+
try {
|
|
19
|
+
routes = parseRoutes(root);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
// If no routes file, we can't do much
|
|
23
|
+
return {
|
|
24
|
+
issues: [
|
|
25
|
+
{
|
|
26
|
+
category: "links",
|
|
27
|
+
severity: "error",
|
|
28
|
+
message: error instanceof Error
|
|
29
|
+
? error.message
|
|
30
|
+
: "Failed to parse routes file",
|
|
31
|
+
location: {
|
|
32
|
+
file: path.join(root, "app", "routes.ts"),
|
|
33
|
+
line: 1,
|
|
34
|
+
column: 1,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
routes: [],
|
|
39
|
+
components: [],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// Find component files to analyze
|
|
43
|
+
const componentFiles = findComponentFiles(root, files);
|
|
44
|
+
// Parse components
|
|
45
|
+
const components = [];
|
|
46
|
+
for (const file of componentFiles) {
|
|
47
|
+
try {
|
|
48
|
+
const analysis = parseComponent(file);
|
|
49
|
+
components.push(analysis);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
// Skip files that can't be parsed
|
|
53
|
+
console.error(`Warning: Could not parse ${file}:`, error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Run checks
|
|
57
|
+
const issues = [];
|
|
58
|
+
const enabledChecks = new Set(checks.length > 0 ? checks : ["links", "forms", "loader", "params", "interactive", "a11y"]);
|
|
59
|
+
if (enabledChecks.has("links")) {
|
|
60
|
+
issues.push(...checkLinks(components, routes));
|
|
61
|
+
}
|
|
62
|
+
if (enabledChecks.has("forms")) {
|
|
63
|
+
issues.push(...checkForms(components, routes, root));
|
|
64
|
+
}
|
|
65
|
+
if (enabledChecks.has("loader")) {
|
|
66
|
+
issues.push(...checkLoaders(components));
|
|
67
|
+
}
|
|
68
|
+
if (enabledChecks.has("params")) {
|
|
69
|
+
issues.push(...checkParams(components, routes, root));
|
|
70
|
+
}
|
|
71
|
+
if (enabledChecks.has("interactive")) {
|
|
72
|
+
issues.push(...checkInteractive(components));
|
|
73
|
+
}
|
|
74
|
+
if (enabledChecks.has("a11y")) {
|
|
75
|
+
issues.push(...checkA11y(components));
|
|
76
|
+
}
|
|
77
|
+
// If specific files were provided, filter issues to only those files
|
|
78
|
+
if (files.length > 0) {
|
|
79
|
+
const targetFiles = new Set(files.map((f) => path.resolve(f)));
|
|
80
|
+
const filteredIssues = issues.filter((issue) => targetFiles.has(path.resolve(issue.location.file)));
|
|
81
|
+
return { issues: filteredIssues, routes, components };
|
|
82
|
+
}
|
|
83
|
+
return { issues, routes, components };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Find all component files to analyze
|
|
87
|
+
*/
|
|
88
|
+
function findComponentFiles(root, specificFiles) {
|
|
89
|
+
if (specificFiles.length > 0) {
|
|
90
|
+
// Analyze only specific files (paths are independent of root)
|
|
91
|
+
return specificFiles.map((f) => path.resolve(f));
|
|
92
|
+
}
|
|
93
|
+
// Find all TSX files in app/routes
|
|
94
|
+
const routesDir = path.join(root, "app", "routes");
|
|
95
|
+
if (!fs.existsSync(routesDir)) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
const files = [];
|
|
99
|
+
walkDir(routesDir, (filePath) => {
|
|
100
|
+
if (filePath.endsWith(".tsx")) {
|
|
101
|
+
files.push(filePath);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return files;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Recursively walk a directory
|
|
108
|
+
*/
|
|
109
|
+
function walkDir(dir, callback) {
|
|
110
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
const fullPath = path.join(dir, entry.name);
|
|
113
|
+
if (entry.isDirectory()) {
|
|
114
|
+
walkDir(fullPath, callback);
|
|
115
|
+
}
|
|
116
|
+
else if (entry.isFile()) {
|
|
117
|
+
callback(fullPath);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a11y-check.d.ts","sourceRoot":"","sources":["../../src/checks/a11y-check.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEpE;;GAEG;AACH,wBAAgB,SAAS,CAAC,WAAW,EAAE,iBAAiB,EAAE,GAAG,aAAa,EAAE,CAM3E"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Placeholder for accessibility checks
|
|
2
|
+
// These require cross-element analysis
|
|
3
|
+
/**
|
|
4
|
+
* Check accessibility issues
|
|
5
|
+
*/
|
|
6
|
+
export function checkA11y(_components) {
|
|
7
|
+
// TODO: Implement accessibility checks
|
|
8
|
+
// - div with onClick but no keyboard handling
|
|
9
|
+
// - Form inputs without label association
|
|
10
|
+
// - Images without alt text
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=a11y-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a11y-check.js","sourceRoot":"","sources":["../../src/checks/a11y-check.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,uCAAuC;AAIvC;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,WAAgC;IACxD,uCAAuC;IACvC,8CAA8C;IAC9C,0CAA0C;IAC1C,4BAA4B;IAC5B,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AnalyzerIssue, ComponentAnalysis, RouteDefinition } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Check form-action wiring
|
|
4
|
+
*/
|
|
5
|
+
export declare function checkForms(components: ComponentAnalysis[], routes: RouteDefinition[], rootDir: string): AnalyzerIssue[];
|
|
6
|
+
//# sourceMappingURL=form-check.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import { matchRoute } from "../parsers/route-parser.js";
|
|
3
|
+
import { parseRouteExports } from "../parsers/action-parser.js";
|
|
4
|
+
/**
|
|
5
|
+
* Check form-action wiring
|
|
6
|
+
*/
|
|
7
|
+
export function checkForms(components, routes, rootDir) {
|
|
8
|
+
const issues = [];
|
|
9
|
+
for (const component of components) {
|
|
10
|
+
for (const form of component.forms) {
|
|
11
|
+
const formIssues = validateForm(form, component, routes, rootDir);
|
|
12
|
+
issues.push(...formIssues);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return issues;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Validate a single form
|
|
19
|
+
*/
|
|
20
|
+
function validateForm(form, component, routes, rootDir) {
|
|
21
|
+
const issues = [];
|
|
22
|
+
// If form has an explicit action, check that route has an action handler
|
|
23
|
+
if (form.action) {
|
|
24
|
+
const targetRoute = matchRoute(form.action, routes);
|
|
25
|
+
if (!targetRoute) {
|
|
26
|
+
issues.push({
|
|
27
|
+
category: "forms",
|
|
28
|
+
severity: "error",
|
|
29
|
+
message: `Form action targets non-existent route: ${form.action}`,
|
|
30
|
+
location: form.location,
|
|
31
|
+
code: `<Form action="${form.action}">`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// Check if the target route file has an action export
|
|
36
|
+
const routeFilePath = path.join(rootDir, "app", targetRoute.file);
|
|
37
|
+
try {
|
|
38
|
+
const exports = parseRouteExports(routeFilePath);
|
|
39
|
+
if (!exports.hasAction) {
|
|
40
|
+
issues.push({
|
|
41
|
+
category: "forms",
|
|
42
|
+
severity: "error",
|
|
43
|
+
message: `Form action targets route without action export`,
|
|
44
|
+
location: form.location,
|
|
45
|
+
code: `<Form action="${form.action}">`,
|
|
46
|
+
suggestion: `Add an action export to ${targetRoute.file}`,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// File doesn't exist or can't be parsed - route-parser should catch this
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// Form submits to current route - check if current file has action
|
|
57
|
+
if (!component.hasAction) {
|
|
58
|
+
issues.push({
|
|
59
|
+
category: "forms",
|
|
60
|
+
severity: "error",
|
|
61
|
+
message: "Form in route with no action export",
|
|
62
|
+
location: form.location,
|
|
63
|
+
code: "<Form>",
|
|
64
|
+
suggestion: "Add an action export to handle form submission",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
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
|
+
return issues;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=form-check.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interactive-check.d.ts","sourceRoot":"","sources":["../../src/checks/interactive-check.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEpE;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,iBAAiB,EAAE,GAC/B,aAAa,EAAE,CAKjB"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Placeholder for interactive element completeness checks
|
|
2
|
+
// This would check for buttons without handlers, etc.
|
|
3
|
+
/**
|
|
4
|
+
* Check interactive element completeness
|
|
5
|
+
*/
|
|
6
|
+
export function checkInteractive(_components) {
|
|
7
|
+
// TODO: Implement interactive element checks
|
|
8
|
+
// - Buttons without onClick, type, or form context
|
|
9
|
+
// - onClick referencing undefined handlers
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=interactive-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interactive-check.js","sourceRoot":"","sources":["../../src/checks/interactive-check.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,sDAAsD;AAItD;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,WAAgC;IAEhC,6CAA6C;IAC7C,mDAAmD;IACnD,2CAA2C;IAC3C,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AnalyzerIssue, ComponentAnalysis, RouteDefinition } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Check all links in components against defined routes
|
|
4
|
+
*/
|
|
5
|
+
export declare function checkLinks(components: ComponentAnalysis[], routes: RouteDefinition[]): AnalyzerIssue[];
|
|
6
|
+
//# sourceMappingURL=link-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-check.d.ts","sourceRoot":"","sources":["../../src/checks/link-check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,eAAe,EAChB,MAAM,aAAa,CAAC;AAQrB;;GAEG;AACH,wBAAgB,UAAU,CACxB,UAAU,EAAE,iBAAiB,EAAE,EAC/B,MAAM,EAAE,eAAe,EAAE,GACxB,aAAa,EAAE,CAcjB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { matchRoute, matchDynamicPattern, getAllRoutePaths, } from "../parsers/route-parser.js";
|
|
2
|
+
import { findBestMatch, formatSuggestion } from "../utils/suggestion.js";
|
|
3
|
+
/**
|
|
4
|
+
* Check all links in components against defined routes
|
|
5
|
+
*/
|
|
6
|
+
export function checkLinks(components, routes) {
|
|
7
|
+
const issues = [];
|
|
8
|
+
const allPaths = getAllRoutePaths(routes);
|
|
9
|
+
for (const component of components) {
|
|
10
|
+
for (const link of component.links) {
|
|
11
|
+
const issue = validateLink(link, routes, allPaths);
|
|
12
|
+
if (issue) {
|
|
13
|
+
issues.push(issue);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return issues;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Validate a single link
|
|
21
|
+
*/
|
|
22
|
+
function validateLink(link, routes, allPaths) {
|
|
23
|
+
if (link.isDynamic) {
|
|
24
|
+
// For dynamic links, check the pattern
|
|
25
|
+
const pattern = link.pattern || link.href;
|
|
26
|
+
const match = matchDynamicPattern(pattern, routes);
|
|
27
|
+
if (!match) {
|
|
28
|
+
const suggestion = findBestMatch(pattern, allPaths);
|
|
29
|
+
return {
|
|
30
|
+
category: "links",
|
|
31
|
+
severity: "error",
|
|
32
|
+
message: "No matching route for dynamic link pattern",
|
|
33
|
+
location: link.location,
|
|
34
|
+
code: `${link.type === "link" ? "href" : link.type}="${link.href}"`,
|
|
35
|
+
suggestion: formatSuggestion(suggestion),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// For static links, do exact matching
|
|
41
|
+
const match = matchRoute(link.href, routes);
|
|
42
|
+
if (!match) {
|
|
43
|
+
const suggestion = findBestMatch(link.href, allPaths);
|
|
44
|
+
return {
|
|
45
|
+
category: "links",
|
|
46
|
+
severity: "error",
|
|
47
|
+
message: "No matching route",
|
|
48
|
+
location: link.location,
|
|
49
|
+
code: `${link.type === "link" ? "href" : link.type}="${link.href}"`,
|
|
50
|
+
suggestion: formatSuggestion(suggestion),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=link-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-check.js","sourceRoot":"","sources":["../../src/checks/link-check.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,UAAU,EACV,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAEzE;;GAEG;AACH,MAAM,UAAU,UAAU,CACxB,UAA+B,EAC/B,MAAyB;IAEzB,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAE1C,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;YACnD,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,YAAY,CACnB,IAAmC,EACnC,MAAyB,EACzB,QAAkB;IAElB,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,uCAAuC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC;QAC1C,MAAM,KAAK,GAAG,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAEnD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,UAAU,GAAG,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YACpD,OAAO;gBACL,QAAQ,EAAE,OAAO;gBACjB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,4CAA4C;gBACrD,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,GAAG;gBACnE,UAAU,EAAE,gBAAgB,CAAC,UAAU,CAAC;aACzC,CAAC;QACJ,CAAC;IACH,CAAC;SAAM,CAAC;QACN,sCAAsC;QACtC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAE5C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YACtD,OAAO;gBACL,QAAQ,EAAE,OAAO;gBACjB,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,mBAAmB;gBAC5B,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,GAAG;gBACnE,UAAU,EAAE,gBAAgB,CAAC,UAAU,CAAC;aACzC,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check loader-component binding
|
|
3
|
+
*/
|
|
4
|
+
export function checkLoaders(components) {
|
|
5
|
+
const issues = [];
|
|
6
|
+
for (const component of components) {
|
|
7
|
+
const componentIssues = validateLoaderUsage(component);
|
|
8
|
+
issues.push(...componentIssues);
|
|
9
|
+
}
|
|
10
|
+
return issues;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Validate loader usage in a component
|
|
14
|
+
*/
|
|
15
|
+
function validateLoaderUsage(component) {
|
|
16
|
+
const issues = [];
|
|
17
|
+
for (const hook of component.dataHooks) {
|
|
18
|
+
if (hook.hook === "useLoaderData" && !component.hasLoader) {
|
|
19
|
+
issues.push({
|
|
20
|
+
category: "loader",
|
|
21
|
+
severity: "error",
|
|
22
|
+
message: "useLoaderData() called but route has no loader",
|
|
23
|
+
location: hook.location,
|
|
24
|
+
code: "useLoaderData()",
|
|
25
|
+
suggestion: "Add a loader function or remove the hook",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if (hook.hook === "useActionData" && !component.hasAction) {
|
|
29
|
+
issues.push({
|
|
30
|
+
category: "loader",
|
|
31
|
+
severity: "warning",
|
|
32
|
+
message: "useActionData() called but route has no action",
|
|
33
|
+
location: hook.location,
|
|
34
|
+
code: "useActionData()",
|
|
35
|
+
suggestion: "Add an action function or remove the hook",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return issues;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=loader-check.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AnalyzerIssue, ComponentAnalysis, RouteDefinition } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Check route parameter consistency
|
|
4
|
+
*/
|
|
5
|
+
export declare function checkParams(components: ComponentAnalysis[], routes: RouteDefinition[], rootDir: string): AnalyzerIssue[];
|
|
6
|
+
//# sourceMappingURL=params-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"params-check.d.ts","sourceRoot":"","sources":["../../src/checks/params-check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,eAAe,EAChB,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,iBAAiB,EAAE,EAC/B,MAAM,EAAE,eAAe,EAAE,EACzB,OAAO,EAAE,MAAM,GACd,aAAa,EAAE,CA4BjB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
/**
|
|
3
|
+
* Check route parameter consistency
|
|
4
|
+
*/
|
|
5
|
+
export function checkParams(components, routes, rootDir) {
|
|
6
|
+
const issues = [];
|
|
7
|
+
// Build a map of file -> route definition
|
|
8
|
+
const fileToRoute = new Map();
|
|
9
|
+
function mapRoutes(routeList) {
|
|
10
|
+
for (const route of routeList) {
|
|
11
|
+
const fullPath = path.join(rootDir, "app", route.file);
|
|
12
|
+
fileToRoute.set(fullPath, route);
|
|
13
|
+
if (route.children) {
|
|
14
|
+
mapRoutes(route.children);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
mapRoutes(routes);
|
|
19
|
+
for (const component of components) {
|
|
20
|
+
const route = fileToRoute.get(component.file);
|
|
21
|
+
if (!route) {
|
|
22
|
+
// Not a route file
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const componentIssues = validateParamUsage(component, route);
|
|
26
|
+
issues.push(...componentIssues);
|
|
27
|
+
}
|
|
28
|
+
return issues;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validate param usage in a component against its route definition
|
|
32
|
+
*/
|
|
33
|
+
function validateParamUsage(component, route) {
|
|
34
|
+
const issues = [];
|
|
35
|
+
const routeParams = new Set(route.params);
|
|
36
|
+
for (const hook of component.dataHooks) {
|
|
37
|
+
if (hook.hook === "useParams" && hook.accessedParams) {
|
|
38
|
+
for (const param of hook.accessedParams) {
|
|
39
|
+
if (!routeParams.has(param)) {
|
|
40
|
+
issues.push({
|
|
41
|
+
category: "params",
|
|
42
|
+
severity: "error",
|
|
43
|
+
message: `useParams() accesses "${param}" but route has no :${param} parameter`,
|
|
44
|
+
location: hook.location,
|
|
45
|
+
code: `useParams().${param}`,
|
|
46
|
+
suggestion: route.params.length > 0
|
|
47
|
+
? `Available params: ${route.params.map((p) => ":" + p).join(", ")}`
|
|
48
|
+
: `Route ${route.path} has no parameters`,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return issues;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=params-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"params-check.js","sourceRoot":"","sources":["../../src/checks/params-check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAO7B;;GAEG;AACH,MAAM,UAAU,WAAW,CACzB,UAA+B,EAC/B,MAAyB,EACzB,OAAe;IAEf,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,0CAA0C;IAC1C,MAAM,WAAW,GAAG,IAAI,GAAG,EAA2B,CAAC;IACvD,SAAS,SAAS,CAAC,SAA4B;QAC7C,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;YAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACvD,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YACjC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnB,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IACD,SAAS,CAAC,MAAM,CAAC,CAAC;IAElB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,mBAAmB;YACnB,SAAS;QACX,CAAC;QAED,MAAM,eAAe,GAAG,kBAAkB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC7D,MAAM,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB,CACzB,SAA4B,EAC5B,KAAsB;IAEtB,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAE1C,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,SAAS,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACrD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;gBACxC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC5B,MAAM,CAAC,IAAI,CAAC;wBACV,QAAQ,EAAE,QAAQ;wBAClB,QAAQ,EAAE,OAAO;wBACjB,OAAO,EAAE,yBAAyB,KAAK,uBAAuB,KAAK,YAAY;wBAC/E,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,IAAI,EAAE,eAAe,KAAK,EAAE;wBAC5B,UAAU,EACR,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;4BACrB,CAAC,CAAC,qBAAqB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;4BACpE,CAAC,CAAC,SAAS,KAAK,CAAC,IAAI,oBAAoB;qBAC9C,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|