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
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RouteDefinition } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parse the app/routes.ts file and extract route definitions
|
|
4
|
+
*/
|
|
5
|
+
export declare function parseRoutes(rootDir: string): RouteDefinition[];
|
|
6
|
+
/**
|
|
7
|
+
* Get all route paths as strings for matching
|
|
8
|
+
*/
|
|
9
|
+
export declare function getAllRoutePaths(routes: RouteDefinition[]): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Check if a URL matches any route
|
|
12
|
+
*/
|
|
13
|
+
export declare function matchRoute(url: string, routes: RouteDefinition[]): RouteDefinition | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Check if a dynamic link pattern could match a route
|
|
16
|
+
* Pattern like "/employees/:param" should match route "/employees/:id"
|
|
17
|
+
*/
|
|
18
|
+
export declare function matchDynamicPattern(pattern: string, routes: RouteDefinition[]): RouteDefinition | undefined;
|
|
19
|
+
//# sourceMappingURL=route-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-parser.d.ts","sourceRoot":"","sources":["../../src/parsers/route-parser.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE,CAqB9D;AAuND;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,EAAE,CAcpE;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,eAAe,EAAE,GACxB,eAAe,GAAG,SAAS,CAa7B;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,eAAe,EAAE,GACxB,eAAe,GAAG,SAAS,CAsD7B"}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
import { parseFile, walkAst } from "../utils/ast-utils.js";
|
|
5
|
+
/**
|
|
6
|
+
* Parse the app/routes.ts file and extract route definitions
|
|
7
|
+
*/
|
|
8
|
+
export function parseRoutes(rootDir) {
|
|
9
|
+
const routesPath = path.join(rootDir, "app", "routes.ts");
|
|
10
|
+
if (!fs.existsSync(routesPath)) {
|
|
11
|
+
throw new Error(`Routes file not found: ${routesPath}`);
|
|
12
|
+
}
|
|
13
|
+
const content = fs.readFileSync(routesPath, "utf-8");
|
|
14
|
+
const sourceFile = parseFile(routesPath, content);
|
|
15
|
+
const routes = [];
|
|
16
|
+
walkAst(sourceFile, (node) => {
|
|
17
|
+
// Look for the default export array
|
|
18
|
+
if (ts.isExportAssignment(node) && node.expression) {
|
|
19
|
+
const extracted = extractRoutesFromExpression(node.expression, rootDir);
|
|
20
|
+
routes.push(...extracted);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return routes;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Extract routes from an expression (array literal or call expression)
|
|
27
|
+
*/
|
|
28
|
+
function extractRoutesFromExpression(expr, rootDir) {
|
|
29
|
+
const routes = [];
|
|
30
|
+
// Array literal: [route(...), route(...)]
|
|
31
|
+
if (ts.isArrayLiteralExpression(expr)) {
|
|
32
|
+
for (const element of expr.elements) {
|
|
33
|
+
const extracted = extractRoutesFromExpression(element, rootDir);
|
|
34
|
+
routes.push(...extracted);
|
|
35
|
+
}
|
|
36
|
+
return routes;
|
|
37
|
+
}
|
|
38
|
+
// "as" expression: [...] satisfies RouteConfig
|
|
39
|
+
if (ts.isSatisfiesExpression(expr) || ts.isAsExpression(expr)) {
|
|
40
|
+
return extractRoutesFromExpression(expr.expression, rootDir);
|
|
41
|
+
}
|
|
42
|
+
// Call expression: route("/path", "file.tsx") or layout("file.tsx", [...])
|
|
43
|
+
if (ts.isCallExpression(expr)) {
|
|
44
|
+
const funcName = ts.isIdentifier(expr.expression)
|
|
45
|
+
? expr.expression.text
|
|
46
|
+
: undefined;
|
|
47
|
+
if (funcName === "route") {
|
|
48
|
+
const route = parseRouteCall(expr, rootDir);
|
|
49
|
+
if (route) {
|
|
50
|
+
routes.push(route);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (funcName === "layout") {
|
|
54
|
+
const layoutRoutes = parseLayoutCall(expr, rootDir);
|
|
55
|
+
routes.push(...layoutRoutes);
|
|
56
|
+
}
|
|
57
|
+
else if (funcName === "index") {
|
|
58
|
+
const route = parseIndexCall(expr, rootDir);
|
|
59
|
+
if (route) {
|
|
60
|
+
routes.push(route);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return routes;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Parse a route() call expression
|
|
68
|
+
* route("/employees", "routes/employees.tsx")
|
|
69
|
+
* route("/employees/:id", "routes/employees.$id.tsx", { id: "employee-detail" })
|
|
70
|
+
*/
|
|
71
|
+
function parseRouteCall(call, rootDir) {
|
|
72
|
+
const args = call.arguments;
|
|
73
|
+
if (args.length < 2) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
// First arg: path
|
|
77
|
+
const pathArg = args[0];
|
|
78
|
+
if (!ts.isStringLiteral(pathArg)) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
const routePath = pathArg.text;
|
|
82
|
+
// Second arg: file path
|
|
83
|
+
const fileArg = args[1];
|
|
84
|
+
if (!ts.isStringLiteral(fileArg)) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
const file = fileArg.text;
|
|
88
|
+
// Third arg (optional): options { id: "..." }
|
|
89
|
+
let id;
|
|
90
|
+
let children;
|
|
91
|
+
if (args.length >= 3) {
|
|
92
|
+
const thirdArg = args[2];
|
|
93
|
+
// Check if it's an options object
|
|
94
|
+
if (ts.isObjectLiteralExpression(thirdArg)) {
|
|
95
|
+
for (const prop of thirdArg.properties) {
|
|
96
|
+
if (ts.isPropertyAssignment(prop) &&
|
|
97
|
+
ts.isIdentifier(prop.name) &&
|
|
98
|
+
prop.name.text === "id" &&
|
|
99
|
+
ts.isStringLiteral(prop.initializer)) {
|
|
100
|
+
id = prop.initializer.text;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Check if it's a children array
|
|
105
|
+
if (ts.isArrayLiteralExpression(thirdArg)) {
|
|
106
|
+
children = extractRoutesFromExpression(thirdArg, rootDir);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Fourth arg could be children if third was options
|
|
110
|
+
if (args.length >= 4) {
|
|
111
|
+
const fourthArg = args[3];
|
|
112
|
+
if (ts.isArrayLiteralExpression(fourthArg)) {
|
|
113
|
+
children = extractRoutesFromExpression(fourthArg, rootDir);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return createRouteDefinition(routePath, file, id, children);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Parse a layout() call expression
|
|
120
|
+
* layout("routes/_layout.tsx", [...])
|
|
121
|
+
*/
|
|
122
|
+
function parseLayoutCall(call, rootDir) {
|
|
123
|
+
const args = call.arguments;
|
|
124
|
+
if (args.length < 2) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
// First arg: layout file path
|
|
128
|
+
const fileArg = args[0];
|
|
129
|
+
if (!ts.isStringLiteral(fileArg)) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
// Second arg: children array
|
|
133
|
+
const childrenArg = args[1];
|
|
134
|
+
const children = extractRoutesFromExpression(childrenArg, rootDir);
|
|
135
|
+
// Layout itself doesn't define a path, so we just return the children
|
|
136
|
+
// but we could track the layout file for other checks
|
|
137
|
+
return children;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Parse an index() call expression
|
|
141
|
+
* index("routes/home.tsx")
|
|
142
|
+
*/
|
|
143
|
+
function parseIndexCall(call, rootDir) {
|
|
144
|
+
const args = call.arguments;
|
|
145
|
+
if (args.length < 1) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
const fileArg = args[0];
|
|
149
|
+
if (!ts.isStringLiteral(fileArg)) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
return createRouteDefinition("/", fileArg.text, undefined, undefined);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Create a RouteDefinition with computed params and pattern
|
|
156
|
+
*/
|
|
157
|
+
function createRouteDefinition(routePath, file, id, children) {
|
|
158
|
+
const params = extractParams(routePath);
|
|
159
|
+
const pattern = pathToRegex(routePath);
|
|
160
|
+
return {
|
|
161
|
+
path: routePath,
|
|
162
|
+
file,
|
|
163
|
+
id,
|
|
164
|
+
params,
|
|
165
|
+
pattern,
|
|
166
|
+
children: children && children.length > 0 ? children : undefined,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Extract param names from a route path
|
|
171
|
+
* "/employees/:id/tasks/:taskId" -> ["id", "taskId"]
|
|
172
|
+
*/
|
|
173
|
+
function extractParams(routePath) {
|
|
174
|
+
const params = [];
|
|
175
|
+
const regex = /:(\w+)/g;
|
|
176
|
+
let match;
|
|
177
|
+
while ((match = regex.exec(routePath)) !== null) {
|
|
178
|
+
params.push(match[1]);
|
|
179
|
+
}
|
|
180
|
+
return params;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Convert a route path to a regex for matching
|
|
184
|
+
* "/employees/:id" -> /^\/employees\/[^/]+$/
|
|
185
|
+
*/
|
|
186
|
+
function pathToRegex(routePath) {
|
|
187
|
+
// Escape special regex chars except :
|
|
188
|
+
let pattern = routePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
189
|
+
// Replace :param with a capturing group that matches any non-slash characters
|
|
190
|
+
pattern = pattern.replace(/:(\w+)/g, "([^/]+)");
|
|
191
|
+
return new RegExp(`^${pattern}$`);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get all route paths as strings for matching
|
|
195
|
+
*/
|
|
196
|
+
export function getAllRoutePaths(routes) {
|
|
197
|
+
const paths = [];
|
|
198
|
+
function collect(routeList) {
|
|
199
|
+
for (const route of routeList) {
|
|
200
|
+
paths.push(route.path);
|
|
201
|
+
if (route.children) {
|
|
202
|
+
collect(route.children);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
collect(routes);
|
|
207
|
+
return paths;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Check if a URL matches any route
|
|
211
|
+
*/
|
|
212
|
+
export function matchRoute(url, routes) {
|
|
213
|
+
for (const route of routes) {
|
|
214
|
+
if (route.pattern.test(url)) {
|
|
215
|
+
return route;
|
|
216
|
+
}
|
|
217
|
+
if (route.children) {
|
|
218
|
+
const childMatch = matchRoute(url, route.children);
|
|
219
|
+
if (childMatch) {
|
|
220
|
+
return childMatch;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Check if a dynamic link pattern could match a route
|
|
228
|
+
* Pattern like "/employees/:param" should match route "/employees/:id"
|
|
229
|
+
*/
|
|
230
|
+
export function matchDynamicPattern(pattern, routes) {
|
|
231
|
+
// Normalize the pattern to compare segment counts and static parts
|
|
232
|
+
const patternParts = pattern.split("/").filter(Boolean);
|
|
233
|
+
for (const route of routes) {
|
|
234
|
+
const routeParts = route.path.split("/").filter(Boolean);
|
|
235
|
+
if (patternParts.length !== routeParts.length) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
let matches = true;
|
|
239
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
240
|
+
const patternPart = patternParts[i];
|
|
241
|
+
const routePart = routeParts[i];
|
|
242
|
+
// Both dynamic - OK
|
|
243
|
+
if (patternPart.startsWith(":") && routePart.startsWith(":")) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
// Both static - must match exactly
|
|
247
|
+
if (!patternPart.startsWith(":") && !routePart.startsWith(":")) {
|
|
248
|
+
if (patternPart !== routePart) {
|
|
249
|
+
matches = false;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
// One dynamic, one static - OK (dynamic in pattern matches any route segment)
|
|
255
|
+
if (patternPart.startsWith(":")) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
// Static in pattern, dynamic in route - doesn't match
|
|
259
|
+
matches = false;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
if (matches) {
|
|
263
|
+
return route;
|
|
264
|
+
}
|
|
265
|
+
// Check children
|
|
266
|
+
if (route.children) {
|
|
267
|
+
const childMatch = matchDynamicPattern(pattern, route.children);
|
|
268
|
+
if (childMatch) {
|
|
269
|
+
return childMatch;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=route-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-parser.js","sourceRoot":"","sources":["../../src/parsers/route-parser.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a route definition parsed from app/routes.ts
|
|
3
|
+
*/
|
|
4
|
+
export interface RouteDefinition {
|
|
5
|
+
/** The URL path pattern (e.g., "/employees/:id") */
|
|
6
|
+
path: string;
|
|
7
|
+
/** The file path relative to app/ (e.g., "routes/employees.$id.tsx") */
|
|
8
|
+
file: string;
|
|
9
|
+
/** Route ID if specified */
|
|
10
|
+
id?: string;
|
|
11
|
+
/** Extracted param names from the path (e.g., ["id"]) */
|
|
12
|
+
params: string[];
|
|
13
|
+
/** Regex for matching URLs against this route */
|
|
14
|
+
pattern: RegExp;
|
|
15
|
+
/** Child routes for layouts */
|
|
16
|
+
children?: RouteDefinition[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Represents a Link found in a component
|
|
20
|
+
*/
|
|
21
|
+
export interface LinkReference {
|
|
22
|
+
/** The href/to value */
|
|
23
|
+
href: string;
|
|
24
|
+
/** Whether this is a template literal with interpolation */
|
|
25
|
+
isDynamic: boolean;
|
|
26
|
+
/** The extracted pattern for dynamic links (e.g., "/employees/:param") */
|
|
27
|
+
pattern?: string;
|
|
28
|
+
/** Source location */
|
|
29
|
+
location: SourceLocation;
|
|
30
|
+
/** The type of link */
|
|
31
|
+
type: "link" | "redirect" | "navigate";
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Represents a Form found in a component
|
|
35
|
+
*/
|
|
36
|
+
export interface FormReference {
|
|
37
|
+
/** The action attribute value */
|
|
38
|
+
action?: string;
|
|
39
|
+
/** The HTTP method */
|
|
40
|
+
method: "get" | "post" | "put" | "patch" | "delete";
|
|
41
|
+
/** Input names within the form */
|
|
42
|
+
inputNames: string[];
|
|
43
|
+
/** Source location */
|
|
44
|
+
location: SourceLocation;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Represents a useLoaderData or useActionData call
|
|
48
|
+
*/
|
|
49
|
+
export interface DataHookReference {
|
|
50
|
+
/** The hook name */
|
|
51
|
+
hook: "useLoaderData" | "useActionData" | "useParams";
|
|
52
|
+
/** Param names accessed (for useParams) */
|
|
53
|
+
accessedParams?: string[];
|
|
54
|
+
/** Source location */
|
|
55
|
+
location: SourceLocation;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Analysis result for a single component file
|
|
59
|
+
*/
|
|
60
|
+
export interface ComponentAnalysis {
|
|
61
|
+
/** The file path */
|
|
62
|
+
file: string;
|
|
63
|
+
/** All Link references found */
|
|
64
|
+
links: LinkReference[];
|
|
65
|
+
/** All Form references found */
|
|
66
|
+
forms: FormReference[];
|
|
67
|
+
/** All data hook references found */
|
|
68
|
+
dataHooks: DataHookReference[];
|
|
69
|
+
/** Whether this file exports a loader function */
|
|
70
|
+
hasLoader: boolean;
|
|
71
|
+
/** Whether this file exports an action function */
|
|
72
|
+
hasAction: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Source location in a file
|
|
76
|
+
*/
|
|
77
|
+
export interface SourceLocation {
|
|
78
|
+
file: string;
|
|
79
|
+
line: number;
|
|
80
|
+
column: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* An issue found by the analyzer
|
|
84
|
+
*/
|
|
85
|
+
export interface AnalyzerIssue {
|
|
86
|
+
/** The check category */
|
|
87
|
+
category: "links" | "forms" | "loader" | "params" | "interactive" | "a11y";
|
|
88
|
+
/** Severity level */
|
|
89
|
+
severity: "error" | "warning";
|
|
90
|
+
/** Human-readable message */
|
|
91
|
+
message: string;
|
|
92
|
+
/** Source location */
|
|
93
|
+
location: SourceLocation;
|
|
94
|
+
/** The problematic code snippet */
|
|
95
|
+
code?: string;
|
|
96
|
+
/** Suggestion for fix (e.g., "Did you mean: /employees/:id?") */
|
|
97
|
+
suggestion?: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Result of running the analyzer
|
|
101
|
+
*/
|
|
102
|
+
export interface AnalyzerResult {
|
|
103
|
+
/** All issues found */
|
|
104
|
+
issues: AnalyzerIssue[];
|
|
105
|
+
/** Routes that were parsed */
|
|
106
|
+
routes: RouteDefinition[];
|
|
107
|
+
/** Components that were analyzed */
|
|
108
|
+
components: ComponentAnalysis[];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* CLI options
|
|
112
|
+
*/
|
|
113
|
+
export interface CliOptions {
|
|
114
|
+
/** Files to check (empty = all) */
|
|
115
|
+
files: string[];
|
|
116
|
+
/** Checks to run (empty = all) */
|
|
117
|
+
checks: string[];
|
|
118
|
+
/** Output format */
|
|
119
|
+
format: "text" | "json";
|
|
120
|
+
/** Root directory of the project */
|
|
121
|
+
root: string;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a TypeScript/TSX file and return its source file AST
|
|
4
|
+
*/
|
|
5
|
+
export declare function parseFile(filePath: string, content: string): ts.SourceFile;
|
|
6
|
+
/**
|
|
7
|
+
* Get line and column from a position in a source file
|
|
8
|
+
*/
|
|
9
|
+
export declare function getLineAndColumn(sourceFile: ts.SourceFile, position: number): {
|
|
10
|
+
line: number;
|
|
11
|
+
column: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Check if a node is a JSX element with a specific tag name
|
|
15
|
+
*/
|
|
16
|
+
export declare function isJsxElementWithName(node: ts.Node, name: string): node is ts.JsxElement | ts.JsxSelfClosingElement;
|
|
17
|
+
/**
|
|
18
|
+
* Get JSX attribute value by name
|
|
19
|
+
*/
|
|
20
|
+
export declare function getJsxAttribute(element: ts.JsxElement | ts.JsxSelfClosingElement, attributeName: string): ts.JsxAttribute | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Extract string value from a JSX attribute
|
|
23
|
+
* Returns undefined for complex expressions that can't be statically analyzed
|
|
24
|
+
*/
|
|
25
|
+
export declare function getJsxAttributeStringValue(attr: ts.JsxAttribute): {
|
|
26
|
+
value: string;
|
|
27
|
+
isDynamic: boolean;
|
|
28
|
+
} | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Check if a node is a call expression to a specific function
|
|
31
|
+
*/
|
|
32
|
+
export declare function isCallTo(node: ts.Node, functionName: string): node is ts.CallExpression;
|
|
33
|
+
/**
|
|
34
|
+
* Check if a function/variable is exported
|
|
35
|
+
*/
|
|
36
|
+
export declare function isExported(node: ts.Node): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Walk the AST and call visitor for each node
|
|
39
|
+
*/
|
|
40
|
+
export declare function walkAst(node: ts.Node, visitor: (node: ts.Node) => void): void;
|
|
41
|
+
//# sourceMappingURL=ast-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../src/utils/ast-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,EAAE,CAAC,UAAU,CAQ1E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,QAAQ,EAAE,MAAM,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAGlC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,IAAI,EAAE,MAAM,GACX,IAAI,IAAI,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,qBAAqB,CAUlD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,qBAAqB,EACjD,aAAa,EAAE,MAAM,GACpB,EAAE,CAAC,YAAY,GAAG,SAAS,CAW7B;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,EAAE,CAAC,YAAY,GACpB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAgCnD;AAmBD;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC,cAAc,CAavF;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,OAAO,CAGjD;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,IAAI,GAAG,IAAI,CAG7E"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a TypeScript/TSX file and return its source file AST
|
|
4
|
+
*/
|
|
5
|
+
export function parseFile(filePath, content) {
|
|
6
|
+
return ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Get line and column from a position in a source file
|
|
10
|
+
*/
|
|
11
|
+
export function getLineAndColumn(sourceFile, position) {
|
|
12
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(position);
|
|
13
|
+
return { line: line + 1, column: character + 1 };
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Check if a node is a JSX element with a specific tag name
|
|
17
|
+
*/
|
|
18
|
+
export function isJsxElementWithName(node, name) {
|
|
19
|
+
if (ts.isJsxElement(node)) {
|
|
20
|
+
const tagName = node.openingElement.tagName;
|
|
21
|
+
return ts.isIdentifier(tagName) && tagName.text === name;
|
|
22
|
+
}
|
|
23
|
+
if (ts.isJsxSelfClosingElement(node)) {
|
|
24
|
+
const tagName = node.tagName;
|
|
25
|
+
return ts.isIdentifier(tagName) && tagName.text === name;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get JSX attribute value by name
|
|
31
|
+
*/
|
|
32
|
+
export function getJsxAttribute(element, attributeName) {
|
|
33
|
+
const attributes = ts.isJsxElement(element)
|
|
34
|
+
? element.openingElement.attributes
|
|
35
|
+
: element.attributes;
|
|
36
|
+
for (const attr of attributes.properties) {
|
|
37
|
+
if (ts.isJsxAttribute(attr) && ts.isIdentifier(attr.name) && attr.name.text === attributeName) {
|
|
38
|
+
return attr;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Extract string value from a JSX attribute
|
|
45
|
+
* Returns undefined for complex expressions that can't be statically analyzed
|
|
46
|
+
*/
|
|
47
|
+
export function getJsxAttributeStringValue(attr) {
|
|
48
|
+
const initializer = attr.initializer;
|
|
49
|
+
if (!initializer) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
// String literal: href="/path"
|
|
53
|
+
if (ts.isStringLiteral(initializer)) {
|
|
54
|
+
return { value: initializer.text, isDynamic: false };
|
|
55
|
+
}
|
|
56
|
+
// JSX expression: href={...}
|
|
57
|
+
if (ts.isJsxExpression(initializer) && initializer.expression) {
|
|
58
|
+
const expr = initializer.expression;
|
|
59
|
+
// String literal inside expression: href={"/path"}
|
|
60
|
+
if (ts.isStringLiteral(expr)) {
|
|
61
|
+
return { value: expr.text, isDynamic: false };
|
|
62
|
+
}
|
|
63
|
+
// Template literal: href={`/employees/${id}`}
|
|
64
|
+
if (ts.isTemplateExpression(expr)) {
|
|
65
|
+
return extractTemplatePattern(expr);
|
|
66
|
+
}
|
|
67
|
+
// No-substitution template: href={`/employees`}
|
|
68
|
+
if (ts.isNoSubstitutionTemplateLiteral(expr)) {
|
|
69
|
+
return { value: expr.text, isDynamic: false };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Extract a pattern from a template literal expression
|
|
76
|
+
* e.g., `/employees/${id}` -> { value: "/employees/${id}", isDynamic: true }
|
|
77
|
+
*/
|
|
78
|
+
function extractTemplatePattern(template) {
|
|
79
|
+
let pattern = template.head.text;
|
|
80
|
+
for (const span of template.templateSpans) {
|
|
81
|
+
// Replace interpolations with :param placeholder
|
|
82
|
+
pattern += ":param" + span.literal.text;
|
|
83
|
+
}
|
|
84
|
+
return { value: pattern, isDynamic: true };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check if a node is a call expression to a specific function
|
|
88
|
+
*/
|
|
89
|
+
export function isCallTo(node, functionName) {
|
|
90
|
+
if (!ts.isCallExpression(node)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const expr = node.expression;
|
|
94
|
+
// Direct call: redirect("/path")
|
|
95
|
+
if (ts.isIdentifier(expr)) {
|
|
96
|
+
return expr.text === functionName;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Check if a function/variable is exported
|
|
102
|
+
*/
|
|
103
|
+
export function isExported(node) {
|
|
104
|
+
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
105
|
+
return modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Walk the AST and call visitor for each node
|
|
109
|
+
*/
|
|
110
|
+
export function walkAst(node, visitor) {
|
|
111
|
+
visitor(node);
|
|
112
|
+
ts.forEachChild(node, (child) => walkAst(child, visitor));
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=ast-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast-utils.js","sourceRoot":"","sources":["../../src/utils/ast-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the best matching suggestion from a list of candidates
|
|
3
|
+
* Returns undefined if no good match is found
|
|
4
|
+
*/
|
|
5
|
+
export declare function findBestMatch(input: string, candidates: string[], maxDistance?: number): string | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Format a "Did you mean?" suggestion
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatSuggestion(match: string | undefined): string | undefined;
|
|
10
|
+
//# sourceMappingURL=suggestion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggestion.d.ts","sourceRoot":"","sources":["../../src/utils/suggestion.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAAE,EACpB,WAAW,SAAI,GACd,MAAM,GAAG,SAAS,CAiBpB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAK9E"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { distance } from "fastest-levenshtein";
|
|
2
|
+
/**
|
|
3
|
+
* Find the best matching suggestion from a list of candidates
|
|
4
|
+
* Returns undefined if no good match is found
|
|
5
|
+
*/
|
|
6
|
+
export function findBestMatch(input, candidates, maxDistance = 3) {
|
|
7
|
+
if (candidates.length === 0) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
let bestMatch;
|
|
11
|
+
let bestDistance = Infinity;
|
|
12
|
+
for (const candidate of candidates) {
|
|
13
|
+
const d = distance(input, candidate);
|
|
14
|
+
if (d < bestDistance && d <= maxDistance) {
|
|
15
|
+
bestDistance = d;
|
|
16
|
+
bestMatch = candidate;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return bestMatch;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Format a "Did you mean?" suggestion
|
|
23
|
+
*/
|
|
24
|
+
export function formatSuggestion(match) {
|
|
25
|
+
if (!match) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return `Did you mean: ${match}?`;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=suggestion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggestion.js","sourceRoot":"","sources":["../../src/utils/suggestion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,KAAa,EACb,UAAoB,EACpB,WAAW,GAAG,CAAC;IAEf,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,SAA6B,CAAC;IAClC,IAAI,YAAY,GAAG,QAAQ,CAAC;IAE5B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QACrC,IAAI,CAAC,GAAG,YAAY,IAAI,CAAC,IAAI,WAAW,EAAE,CAAC;YACzC,YAAY,GAAG,CAAC,CAAC;YACjB,SAAS,GAAG,SAAS,CAAC;QACxB,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAyB;IACxD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,iBAAiB,KAAK,GAAG,CAAC;AACnC,CAAC"}
|