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.
Files changed (62) hide show
  1. package/README.md +52 -0
  2. package/dist/analyzer.d.ts +6 -0
  3. package/dist/analyzer.d.ts.map +1 -0
  4. package/dist/analyzer.js +121 -0
  5. package/dist/analyzer.js.map +1 -0
  6. package/dist/checks/a11y-check.d.ts +6 -0
  7. package/dist/checks/a11y-check.d.ts.map +1 -0
  8. package/dist/checks/a11y-check.js +13 -0
  9. package/dist/checks/a11y-check.js.map +1 -0
  10. package/dist/checks/form-check.d.ts +6 -0
  11. package/dist/checks/form-check.d.ts.map +1 -0
  12. package/dist/checks/form-check.js +74 -0
  13. package/dist/checks/form-check.js.map +1 -0
  14. package/dist/checks/interactive-check.d.ts +6 -0
  15. package/dist/checks/interactive-check.d.ts.map +1 -0
  16. package/dist/checks/interactive-check.js +12 -0
  17. package/dist/checks/interactive-check.js.map +1 -0
  18. package/dist/checks/link-check.d.ts +6 -0
  19. package/dist/checks/link-check.d.ts.map +1 -0
  20. package/dist/checks/link-check.js +56 -0
  21. package/dist/checks/link-check.js.map +1 -0
  22. package/dist/checks/loader-check.d.ts +6 -0
  23. package/dist/checks/loader-check.d.ts.map +1 -0
  24. package/dist/checks/loader-check.js +41 -0
  25. package/dist/checks/loader-check.js.map +1 -0
  26. package/dist/checks/params-check.d.ts +6 -0
  27. package/dist/checks/params-check.d.ts.map +1 -0
  28. package/dist/checks/params-check.js +56 -0
  29. package/dist/checks/params-check.js.map +1 -0
  30. package/dist/cli.d.ts +3 -0
  31. package/dist/cli.d.ts.map +1 -0
  32. package/dist/cli.js +151 -0
  33. package/dist/cli.js.map +1 -0
  34. package/dist/index.d.ts +10 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +10 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/parsers/action-parser.d.ts +17 -0
  39. package/dist/parsers/action-parser.d.ts.map +1 -0
  40. package/dist/parsers/action-parser.js +53 -0
  41. package/dist/parsers/action-parser.js.map +1 -0
  42. package/dist/parsers/component-parser.d.ts +6 -0
  43. package/dist/parsers/component-parser.d.ts.map +1 -0
  44. package/dist/parsers/component-parser.js +354 -0
  45. package/dist/parsers/component-parser.js.map +1 -0
  46. package/dist/parsers/route-parser.d.ts +19 -0
  47. package/dist/parsers/route-parser.d.ts.map +1 -0
  48. package/dist/parsers/route-parser.js +275 -0
  49. package/dist/parsers/route-parser.js.map +1 -0
  50. package/dist/types.d.ts +123 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +2 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/utils/ast-utils.d.ts +41 -0
  55. package/dist/utils/ast-utils.d.ts.map +1 -0
  56. package/dist/utils/ast-utils.js +114 -0
  57. package/dist/utils/ast-utils.js.map +1 -0
  58. package/dist/utils/suggestion.d.ts +10 -0
  59. package/dist/utils/suggestion.d.ts.map +1 -0
  60. package/dist/utils/suggestion.js +30 -0
  61. package/dist/utils/suggestion.js.map +1 -0
  62. 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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"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -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":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,OAAe;IACzD,OAAO,EAAE,CAAC,gBAAgB,CACxB,QAAQ,EACR,OAAO,EACP,EAAE,CAAC,YAAY,CAAC,MAAM,EACtB,IAAI,EACJ,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CACjE,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,UAAyB,EACzB,QAAgB;IAEhB,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,UAAU,CAAC,6BAA6B,CAAC,QAAQ,CAAC,CAAC;IAC/E,OAAO,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,EAAE,MAAM,EAAE,SAAS,GAAG,CAAC,EAAE,CAAC;AACnD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAa,EACb,IAAY;IAEZ,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC;QAC5C,OAAO,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC;IAC3D,CAAC;IACD,IAAI,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,OAAO,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC;IAC3D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAiD,EACjD,aAAqB;IAErB,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC;QACzC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,UAAU;QACnC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;IAEvB,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,UAAU,EAAE,CAAC;QACzC,IAAI,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YAC9F,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CACxC,IAAqB;IAErB,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;IACrC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,+BAA+B;IAC/B,IAAI,EAAE,CAAC,eAAe,CAAC,WAAW,CAAC,EAAE,CAAC;QACpC,OAAO,EAAE,KAAK,EAAE,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IACvD,CAAC;IAED,6BAA6B;IAC7B,IAAI,EAAE,CAAC,eAAe,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC,UAAU,EAAE,CAAC;QAC9D,MAAM,IAAI,GAAG,WAAW,CAAC,UAAU,CAAC;QAEpC,mDAAmD;QACnD,IAAI,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;QAChD,CAAC;QAED,8CAA8C;QAC9C,IAAI,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC;QAED,gDAAgD;QAChD,IAAI,EAAE,CAAC,+BAA+B,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;QAChD,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,SAAS,sBAAsB,CAC7B,QAA+B;IAE/B,IAAI,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;IAEjC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;QAC1C,iDAAiD;QACjD,OAAO,IAAI,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC1C,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAC7C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAa,EAAE,YAAoB;IAC1D,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC;IAE7B,iCAAiC;IACjC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,IAAI,KAAK,YAAY,CAAC;IACpC,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,IAAa;IACtC,MAAM,SAAS,GAAG,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAChF,OAAO,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC;AACjF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,IAAa,EAAE,OAAgC;IACrE,OAAO,CAAC,IAAI,CAAC,CAAC;IACd,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;AAC5D,CAAC"}
@@ -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"}