route-intelligence-vscode 1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # route-intelligence-vscode
2
+
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - start of my project
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - Updated dependencies
13
+ - Updated dependencies
14
+ - @route-intelligence/core@2.0.0
15
+ - @route-intelligence/next@2.0.0
16
+ - @route-intelligence/shared@2.0.0
@@ -0,0 +1,6 @@
1
+ import * as vscode from 'vscode';
2
+
3
+ declare function activate(context: vscode.ExtensionContext): void;
4
+ declare function deactivate(): void;
5
+
6
+ export { activate, deactivate };
@@ -0,0 +1,186 @@
1
+ // src/extension.ts
2
+ import { createAnalyzer, exportJson } from "@route-intelligence/core";
3
+ import { NextPlugin } from "@route-intelligence/next";
4
+ import * as vscode from "vscode";
5
+ var cachedGraph = null;
6
+ var diagnosticCollection = vscode.languages.createDiagnosticCollection("route-intelligence");
7
+ async function analyzeWorkspace() {
8
+ const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
9
+ if (!workspaceRoot) return null;
10
+ const analyzer = createAnalyzer({
11
+ root: workspaceRoot,
12
+ plugins: [NextPlugin()],
13
+ include: ["app/**", "pages/**", "src/**", "middleware.ts"],
14
+ exclude: ["**/node_modules/**", "**/.next/**"]
15
+ });
16
+ const result = await analyzer.analyze();
17
+ cachedGraph = JSON.parse(
18
+ exportJson(result.graph, workspaceRoot)
19
+ );
20
+ updateDiagnostics(result.diagnostics);
21
+ return cachedGraph;
22
+ }
23
+ function updateDiagnostics(diagnostics) {
24
+ diagnosticCollection.clear();
25
+ const byFile = /* @__PURE__ */ new Map();
26
+ for (const d of diagnostics) {
27
+ if (!d.loc?.filePath) continue;
28
+ const diags = byFile.get(d.loc.filePath) ?? [];
29
+ diags.push(
30
+ new vscode.Diagnostic(
31
+ new vscode.Range(
32
+ Math.max(0, d.loc.line - 1),
33
+ d.loc.column,
34
+ Math.max(0, d.loc.line - 1),
35
+ d.loc.column + 1
36
+ ),
37
+ d.message,
38
+ d.severity === "error" ? vscode.DiagnosticSeverity.Error : vscode.DiagnosticSeverity.Warning
39
+ )
40
+ );
41
+ byFile.set(d.loc.filePath, diags);
42
+ }
43
+ for (const [file, diags] of byFile) {
44
+ diagnosticCollection.set(vscode.Uri.file(file), diags);
45
+ }
46
+ }
47
+ var RouteTreeProvider = class {
48
+ _onDidChangeTreeData = new vscode.EventEmitter();
49
+ onDidChangeTreeData = this._onDidChangeTreeData.event;
50
+ refresh() {
51
+ this._onDidChangeTreeData.fire(void 0);
52
+ }
53
+ getTreeItem(element) {
54
+ return element;
55
+ }
56
+ getChildren(element) {
57
+ if (!cachedGraph) return [];
58
+ const graph = cachedGraph;
59
+ if (!element) {
60
+ return graph.nodes.filter((n) => n.attributes.type === "route").map(
61
+ (n) => new RouteTreeItem(n.attributes.path, n.id, vscode.TreeItemCollapsibleState.Collapsed)
62
+ );
63
+ }
64
+ const incoming = graph.edges.filter(
65
+ (e) => e.target === element.id && e.attributes.type === "navigation"
66
+ );
67
+ const outgoing = graph.edges.filter(
68
+ (e) => e.source === element.id && e.attributes.type === "navigation"
69
+ );
70
+ return [
71
+ ...incoming.map((e) => {
72
+ const source = graph.nodes.find((n) => n.id === e.source);
73
+ return new RouteTreeItem(
74
+ `\u2190 ${source?.attributes.path ?? e.source}`,
75
+ e.source,
76
+ vscode.TreeItemCollapsibleState.None
77
+ );
78
+ }),
79
+ ...outgoing.map((e) => {
80
+ const target = graph.nodes.find((n) => n.id === e.target);
81
+ return new RouteTreeItem(
82
+ `\u2192 ${target?.attributes.path ?? e.target}`,
83
+ e.target,
84
+ vscode.TreeItemCollapsibleState.None
85
+ );
86
+ })
87
+ ];
88
+ }
89
+ };
90
+ var RouteTreeItem = class extends vscode.TreeItem {
91
+ constructor(label, id, collapsibleState) {
92
+ super(label, collapsibleState);
93
+ this.id = id;
94
+ }
95
+ id;
96
+ };
97
+ function activate(context) {
98
+ const treeProvider = new RouteTreeProvider();
99
+ vscode.window.registerTreeDataProvider("routeIntelligenceRoutes", treeProvider);
100
+ const hoverProvider = vscode.languages.registerHoverProvider(
101
+ ["typescript", "typescriptreact", "javascript", "javascriptreact"],
102
+ {
103
+ provideHover(document, position) {
104
+ if (!cachedGraph) return null;
105
+ const line = document.lineAt(position.line).text;
106
+ const hrefMatch = line.match(/href=["']([^"']+)["']/);
107
+ if (hrefMatch?.[1]) {
108
+ const path = hrefMatch[1];
109
+ const node = cachedGraph.nodes.find((n) => n.attributes.path === path);
110
+ if (node) {
111
+ return new vscode.Hover(
112
+ [
113
+ `**Route:** ${node.attributes.path}`,
114
+ `Type: ${node.attributes.type}`,
115
+ `File: ${node.attributes.filePath}`,
116
+ node.attributes.isDead ? "\u26A0 Dead route" : ""
117
+ ].filter(Boolean).join("\n\n")
118
+ );
119
+ }
120
+ return new vscode.Hover(`\u26A0 Unknown route: ${path}`);
121
+ }
122
+ return null;
123
+ }
124
+ }
125
+ );
126
+ const definitionProvider = vscode.languages.registerDefinitionProvider(
127
+ ["typescript", "typescriptreact", "javascript", "javascriptreact"],
128
+ {
129
+ provideDefinition(document, position) {
130
+ if (!cachedGraph) return null;
131
+ const line = document.lineAt(position.line).text;
132
+ const hrefMatch = line.match(/href=["']([^"']+)["']/);
133
+ if (!hrefMatch?.[1]) return null;
134
+ const node = cachedGraph.nodes.find((n) => n.attributes.path === hrefMatch[1]);
135
+ if (node) {
136
+ return new vscode.Location(
137
+ vscode.Uri.file(node.attributes.filePath),
138
+ new vscode.Position(0, 0)
139
+ );
140
+ }
141
+ return null;
142
+ }
143
+ }
144
+ );
145
+ context.subscriptions.push(
146
+ vscode.commands.registerCommand("route-intelligence.analyze", async () => {
147
+ await vscode.window.withProgress(
148
+ { location: vscode.ProgressLocation.Notification, title: "Analyzing routes..." },
149
+ async () => {
150
+ await analyzeWorkspace();
151
+ treeProvider.refresh();
152
+ }
153
+ );
154
+ vscode.window.showInformationMessage("Route analysis complete");
155
+ }),
156
+ vscode.commands.registerCommand("route-intelligence.showGraph", () => {
157
+ const panel = vscode.window.createWebviewPanel(
158
+ "routeGraph",
159
+ "Route Graph",
160
+ vscode.ViewColumn.One,
161
+ { enableScripts: true }
162
+ );
163
+ panel.webview.html = getWebviewContent();
164
+ }),
165
+ hoverProvider,
166
+ definitionProvider,
167
+ diagnosticCollection
168
+ );
169
+ void analyzeWorkspace().then(() => treeProvider.refresh());
170
+ vscode.workspace.onDidSaveTextDocument(async (doc) => {
171
+ if (/\.(tsx?|jsx?)$/.test(doc.fileName)) {
172
+ await analyzeWorkspace();
173
+ treeProvider.refresh();
174
+ }
175
+ });
176
+ }
177
+ function getWebviewContent() {
178
+ return "<!DOCTYPE html><html><body><h1>Route Graph</h1><p>Run route-intelligence graph for full visualization</p></body></html>";
179
+ }
180
+ function deactivate() {
181
+ diagnosticCollection.dispose();
182
+ }
183
+ export {
184
+ activate,
185
+ deactivate
186
+ };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "route-intelligence-vscode",
3
+ "displayName": "Route Intelligence",
4
+ "description": "Routing intelligence for React and Next.js applications",
5
+ "version": "1.1.0",
6
+ "publisher": "route-intelligence",
7
+ "engines": {
8
+ "vscode": "^1.90.0"
9
+ },
10
+ "categories": ["Other", "Linters"],
11
+ "activationEvents": [
12
+ "onLanguage:typescript",
13
+ "onLanguage:typescriptreact",
14
+ "onLanguage:javascript",
15
+ "onLanguage:javascriptreact"
16
+ ],
17
+ "type": "module",
18
+ "main": "./dist/extension.js",
19
+ "contributes": {
20
+ "commands": [
21
+ {
22
+ "command": "route-intelligence.analyze",
23
+ "title": "Route Intelligence: Analyze Project"
24
+ },
25
+ {
26
+ "command": "route-intelligence.showGraph",
27
+ "title": "Route Intelligence: Show Route Graph"
28
+ }
29
+ ],
30
+ "views": {
31
+ "explorer": [
32
+ {
33
+ "id": "routeIntelligenceRoutes",
34
+ "name": "Route Intelligence"
35
+ }
36
+ ]
37
+ }
38
+ },
39
+ "scripts": {
40
+ "build": "tsup src/extension.ts --format esm --dts --clean --external vscode",
41
+ "typecheck": "tsc --noEmit"
42
+ },
43
+ "dependencies": {
44
+ "@route-intelligence/core": "*",
45
+ "@route-intelligence/next": "*",
46
+ "@route-intelligence/shared": "*"
47
+ },
48
+ "devDependencies": {
49
+ "@route-intelligence/tsconfig": "*",
50
+ "@types/node": "^22.15.32",
51
+ "@types/vscode": "^1.90.0",
52
+ "tsup": "^8.4.0",
53
+ "typescript": "^5.8.3"
54
+ },
55
+ "license": "MIT"
56
+ }
@@ -0,0 +1,222 @@
1
+ import { type RouteGraph, createAnalyzer, exportJson } from '@route-intelligence/core';
2
+ import { NextPlugin } from '@route-intelligence/next';
3
+ import type { SerializedGraph } from '@route-intelligence/shared';
4
+ import * as vscode from 'vscode';
5
+
6
+ let cachedGraph: SerializedGraph | null = null;
7
+ const diagnosticCollection = vscode.languages.createDiagnosticCollection('route-intelligence');
8
+
9
+ async function analyzeWorkspace(): Promise<SerializedGraph | null> {
10
+ const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
11
+ if (!workspaceRoot) return null;
12
+
13
+ const analyzer = createAnalyzer({
14
+ root: workspaceRoot,
15
+ plugins: [NextPlugin()],
16
+ include: ['app/**', 'pages/**', 'src/**', 'middleware.ts'],
17
+ exclude: ['**/node_modules/**', '**/.next/**'],
18
+ });
19
+
20
+ const result = await analyzer.analyze();
21
+ cachedGraph = JSON.parse(
22
+ exportJson(result.graph as RouteGraph, workspaceRoot),
23
+ ) as SerializedGraph;
24
+
25
+ updateDiagnostics(result.diagnostics);
26
+ return cachedGraph;
27
+ }
28
+
29
+ function updateDiagnostics(
30
+ diagnostics: Array<{
31
+ severity: string;
32
+ message: string;
33
+ loc?: { filePath: string; line: number; column: number };
34
+ }>,
35
+ ) {
36
+ diagnosticCollection.clear();
37
+ const byFile = new Map<string, vscode.Diagnostic[]>();
38
+
39
+ for (const d of diagnostics) {
40
+ if (!d.loc?.filePath) continue;
41
+ const diags = byFile.get(d.loc.filePath) ?? [];
42
+ diags.push(
43
+ new vscode.Diagnostic(
44
+ new vscode.Range(
45
+ Math.max(0, d.loc.line - 1),
46
+ d.loc.column,
47
+ Math.max(0, d.loc.line - 1),
48
+ d.loc.column + 1,
49
+ ),
50
+ d.message,
51
+ d.severity === 'error'
52
+ ? vscode.DiagnosticSeverity.Error
53
+ : vscode.DiagnosticSeverity.Warning,
54
+ ),
55
+ );
56
+ byFile.set(d.loc.filePath, diags);
57
+ }
58
+
59
+ for (const [file, diags] of byFile) {
60
+ diagnosticCollection.set(vscode.Uri.file(file), diags);
61
+ }
62
+ }
63
+
64
+ class RouteTreeProvider implements vscode.TreeDataProvider<RouteTreeItem> {
65
+ private _onDidChangeTreeData = new vscode.EventEmitter<RouteTreeItem | undefined>();
66
+ readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
67
+
68
+ refresh(): void {
69
+ this._onDidChangeTreeData.fire(undefined);
70
+ }
71
+
72
+ getTreeItem(element: RouteTreeItem): vscode.TreeItem {
73
+ return element;
74
+ }
75
+
76
+ getChildren(element?: RouteTreeItem): RouteTreeItem[] {
77
+ if (!cachedGraph) return [];
78
+ const graph = cachedGraph;
79
+ if (!element) {
80
+ return graph.nodes
81
+ .filter((n) => n.attributes.type === 'route')
82
+ .map(
83
+ (n) =>
84
+ new RouteTreeItem(n.attributes.path, n.id, vscode.TreeItemCollapsibleState.Collapsed),
85
+ );
86
+ }
87
+
88
+ const incoming = graph.edges.filter(
89
+ (e) => e.target === element.id && e.attributes.type === 'navigation',
90
+ );
91
+ const outgoing = graph.edges.filter(
92
+ (e) => e.source === element.id && e.attributes.type === 'navigation',
93
+ );
94
+
95
+ return [
96
+ ...incoming.map((e) => {
97
+ const source = graph.nodes.find((n) => n.id === e.source);
98
+ return new RouteTreeItem(
99
+ `← ${source?.attributes.path ?? e.source}`,
100
+ e.source,
101
+ vscode.TreeItemCollapsibleState.None,
102
+ );
103
+ }),
104
+ ...outgoing.map((e) => {
105
+ const target = graph.nodes.find((n) => n.id === e.target);
106
+ return new RouteTreeItem(
107
+ `→ ${target?.attributes.path ?? e.target}`,
108
+ e.target,
109
+ vscode.TreeItemCollapsibleState.None,
110
+ );
111
+ }),
112
+ ];
113
+ }
114
+ }
115
+
116
+ class RouteTreeItem extends vscode.TreeItem {
117
+ constructor(
118
+ label: string,
119
+ public readonly id: string,
120
+ collapsibleState: vscode.TreeItemCollapsibleState,
121
+ ) {
122
+ super(label, collapsibleState);
123
+ }
124
+ }
125
+
126
+ export function activate(context: vscode.ExtensionContext) {
127
+ const treeProvider = new RouteTreeProvider();
128
+ vscode.window.registerTreeDataProvider('routeIntelligenceRoutes', treeProvider);
129
+
130
+ const hoverProvider = vscode.languages.registerHoverProvider(
131
+ ['typescript', 'typescriptreact', 'javascript', 'javascriptreact'],
132
+ {
133
+ provideHover(document, position) {
134
+ if (!cachedGraph) return null;
135
+ const line = document.lineAt(position.line).text;
136
+
137
+ const hrefMatch = line.match(/href=["']([^"']+)["']/);
138
+ if (hrefMatch?.[1]) {
139
+ const path = hrefMatch[1];
140
+ const node = cachedGraph.nodes.find((n) => n.attributes.path === path);
141
+ if (node) {
142
+ return new vscode.Hover(
143
+ [
144
+ `**Route:** ${node.attributes.path}`,
145
+ `Type: ${node.attributes.type}`,
146
+ `File: ${node.attributes.filePath}`,
147
+ node.attributes.isDead ? '⚠ Dead route' : '',
148
+ ]
149
+ .filter(Boolean)
150
+ .join('\n\n'),
151
+ );
152
+ }
153
+ return new vscode.Hover(`⚠ Unknown route: ${path}`);
154
+ }
155
+ return null;
156
+ },
157
+ },
158
+ );
159
+
160
+ const definitionProvider = vscode.languages.registerDefinitionProvider(
161
+ ['typescript', 'typescriptreact', 'javascript', 'javascriptreact'],
162
+ {
163
+ provideDefinition(document, position) {
164
+ if (!cachedGraph) return null;
165
+ const line = document.lineAt(position.line).text;
166
+ const hrefMatch = line.match(/href=["']([^"']+)["']/);
167
+ if (!hrefMatch?.[1]) return null;
168
+
169
+ const node = cachedGraph.nodes.find((n) => n.attributes.path === hrefMatch[1]);
170
+ if (node) {
171
+ return new vscode.Location(
172
+ vscode.Uri.file(node.attributes.filePath),
173
+ new vscode.Position(0, 0),
174
+ );
175
+ }
176
+ return null;
177
+ },
178
+ },
179
+ );
180
+
181
+ context.subscriptions.push(
182
+ vscode.commands.registerCommand('route-intelligence.analyze', async () => {
183
+ await vscode.window.withProgress(
184
+ { location: vscode.ProgressLocation.Notification, title: 'Analyzing routes...' },
185
+ async () => {
186
+ await analyzeWorkspace();
187
+ treeProvider.refresh();
188
+ },
189
+ );
190
+ vscode.window.showInformationMessage('Route analysis complete');
191
+ }),
192
+ vscode.commands.registerCommand('route-intelligence.showGraph', () => {
193
+ const panel = vscode.window.createWebviewPanel(
194
+ 'routeGraph',
195
+ 'Route Graph',
196
+ vscode.ViewColumn.One,
197
+ { enableScripts: true },
198
+ );
199
+ panel.webview.html = getWebviewContent();
200
+ }),
201
+ hoverProvider,
202
+ definitionProvider,
203
+ diagnosticCollection,
204
+ );
205
+
206
+ void analyzeWorkspace().then(() => treeProvider.refresh());
207
+
208
+ vscode.workspace.onDidSaveTextDocument(async (doc) => {
209
+ if (/\.(tsx?|jsx?)$/.test(doc.fileName)) {
210
+ await analyzeWorkspace();
211
+ treeProvider.refresh();
212
+ }
213
+ });
214
+ }
215
+
216
+ function getWebviewContent(): string {
217
+ return '<!DOCTYPE html><html><body><h1>Route Graph</h1><p>Run route-intelligence graph for full visualization</p></body></html>';
218
+ }
219
+
220
+ export function deactivate() {
221
+ diagnosticCollection.dispose();
222
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../tooling/tsconfig/node.json",
3
+ "compilerOptions": { "outDir": "dist", "rootDir": "src" },
4
+ "include": ["src/**/*"]
5
+ }