lsp-pi 1.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 +178 -0
- package/lsp-core.ts +1125 -0
- package/lsp-tool.ts +339 -0
- package/lsp.ts +575 -0
- package/package.json +46 -0
- package/tests/index.test.ts +235 -0
- package/tests/lsp-integration.test.ts +602 -0
- package/tests/lsp.test.ts +898 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for index.ts formatting functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Test utilities
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
const tests: Array<{ name: string; fn: () => void | Promise<void> }> = [];
|
|
10
|
+
|
|
11
|
+
function test(name: string, fn: () => void | Promise<void>) {
|
|
12
|
+
tests.push({ name, fn });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function assertEqual<T>(actual: T, expected: T, message?: string) {
|
|
16
|
+
const a = JSON.stringify(actual);
|
|
17
|
+
const e = JSON.stringify(expected);
|
|
18
|
+
if (a !== e) throw new Error(message || `Expected ${e}, got ${a}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Import the module to test internal functions
|
|
23
|
+
// We need to test via the execute function since formatters are private
|
|
24
|
+
// Or we can extract and test the logic directly
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
import { uriToPath, findSymbolPosition, formatDiagnostic, filterDiagnosticsBySeverity } from "../lsp-core.js";
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// uriToPath tests
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
test("uriToPath: converts file:// URI to path", () => {
|
|
34
|
+
const result = uriToPath("file:///Users/test/file.ts");
|
|
35
|
+
assertEqual(result, "/Users/test/file.ts");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("uriToPath: handles encoded characters", () => {
|
|
39
|
+
const result = uriToPath("file:///Users/test/my%20file.ts");
|
|
40
|
+
assertEqual(result, "/Users/test/my file.ts");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("uriToPath: passes through non-file URIs", () => {
|
|
44
|
+
const result = uriToPath("/some/path.ts");
|
|
45
|
+
assertEqual(result, "/some/path.ts");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("uriToPath: handles invalid URIs gracefully", () => {
|
|
49
|
+
const result = uriToPath("not-a-valid-uri");
|
|
50
|
+
assertEqual(result, "not-a-valid-uri");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// findSymbolPosition tests
|
|
55
|
+
// ============================================================================
|
|
56
|
+
|
|
57
|
+
test("findSymbolPosition: finds exact match", () => {
|
|
58
|
+
const symbols = [
|
|
59
|
+
{ name: "greet", range: { start: { line: 5, character: 10 }, end: { line: 5, character: 15 } }, selectionRange: { start: { line: 5, character: 10 }, end: { line: 5, character: 15 } }, kind: 12, children: [] },
|
|
60
|
+
{ name: "hello", range: { start: { line: 10, character: 0 }, end: { line: 10, character: 5 } }, selectionRange: { start: { line: 10, character: 0 }, end: { line: 10, character: 5 } }, kind: 12, children: [] },
|
|
61
|
+
];
|
|
62
|
+
const pos = findSymbolPosition(symbols as any, "greet");
|
|
63
|
+
assertEqual(pos, { line: 5, character: 10 });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("findSymbolPosition: finds partial match", () => {
|
|
67
|
+
const symbols = [
|
|
68
|
+
{ name: "getUserName", range: { start: { line: 3, character: 0 }, end: { line: 3, character: 11 } }, selectionRange: { start: { line: 3, character: 0 }, end: { line: 3, character: 11 } }, kind: 12, children: [] },
|
|
69
|
+
];
|
|
70
|
+
const pos = findSymbolPosition(symbols as any, "user");
|
|
71
|
+
assertEqual(pos, { line: 3, character: 0 });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("findSymbolPosition: prefers exact over partial", () => {
|
|
75
|
+
const symbols = [
|
|
76
|
+
{ name: "userName", range: { start: { line: 1, character: 0 }, end: { line: 1, character: 8 } }, selectionRange: { start: { line: 1, character: 0 }, end: { line: 1, character: 8 } }, kind: 12, children: [] },
|
|
77
|
+
{ name: "user", range: { start: { line: 5, character: 0 }, end: { line: 5, character: 4 } }, selectionRange: { start: { line: 5, character: 0 }, end: { line: 5, character: 4 } }, kind: 12, children: [] },
|
|
78
|
+
];
|
|
79
|
+
const pos = findSymbolPosition(symbols as any, "user");
|
|
80
|
+
assertEqual(pos, { line: 5, character: 0 });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("findSymbolPosition: searches nested children", () => {
|
|
84
|
+
const symbols = [
|
|
85
|
+
{
|
|
86
|
+
name: "MyClass",
|
|
87
|
+
range: { start: { line: 0, character: 0 }, end: { line: 10, character: 0 } },
|
|
88
|
+
selectionRange: { start: { line: 0, character: 0 }, end: { line: 0, character: 7 } },
|
|
89
|
+
kind: 5,
|
|
90
|
+
children: [
|
|
91
|
+
{ name: "myMethod", range: { start: { line: 2, character: 2 }, end: { line: 4, character: 2 } }, selectionRange: { start: { line: 2, character: 2 }, end: { line: 2, character: 10 } }, kind: 6, children: [] },
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
const pos = findSymbolPosition(symbols as any, "myMethod");
|
|
96
|
+
assertEqual(pos, { line: 2, character: 2 });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("findSymbolPosition: returns null for no match", () => {
|
|
100
|
+
const symbols = [
|
|
101
|
+
{ name: "foo", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 3 } }, selectionRange: { start: { line: 0, character: 0 }, end: { line: 0, character: 3 } }, kind: 12, children: [] },
|
|
102
|
+
];
|
|
103
|
+
const pos = findSymbolPosition(symbols as any, "bar");
|
|
104
|
+
assertEqual(pos, null);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("findSymbolPosition: case insensitive", () => {
|
|
108
|
+
const symbols = [
|
|
109
|
+
{ name: "MyFunction", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 10 } }, selectionRange: { start: { line: 0, character: 0 }, end: { line: 0, character: 10 } }, kind: 12, children: [] },
|
|
110
|
+
];
|
|
111
|
+
const pos = findSymbolPosition(symbols as any, "myfunction");
|
|
112
|
+
assertEqual(pos, { line: 0, character: 0 });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// formatDiagnostic tests
|
|
117
|
+
// ============================================================================
|
|
118
|
+
|
|
119
|
+
test("formatDiagnostic: formats error", () => {
|
|
120
|
+
const diag = {
|
|
121
|
+
range: { start: { line: 5, character: 10 }, end: { line: 5, character: 15 } },
|
|
122
|
+
message: "Type 'number' is not assignable to type 'string'",
|
|
123
|
+
severity: 1,
|
|
124
|
+
};
|
|
125
|
+
const result = formatDiagnostic(diag as any);
|
|
126
|
+
assertEqual(result, "ERROR [6:11] Type 'number' is not assignable to type 'string'");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("formatDiagnostic: formats warning", () => {
|
|
130
|
+
const diag = {
|
|
131
|
+
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 5 } },
|
|
132
|
+
message: "Unused variable",
|
|
133
|
+
severity: 2,
|
|
134
|
+
};
|
|
135
|
+
const result = formatDiagnostic(diag as any);
|
|
136
|
+
assertEqual(result, "WARN [1:1] Unused variable");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("formatDiagnostic: formats info", () => {
|
|
140
|
+
const diag = {
|
|
141
|
+
range: { start: { line: 2, character: 4 }, end: { line: 2, character: 10 } },
|
|
142
|
+
message: "Consider using const",
|
|
143
|
+
severity: 3,
|
|
144
|
+
};
|
|
145
|
+
const result = formatDiagnostic(diag as any);
|
|
146
|
+
assertEqual(result, "INFO [3:5] Consider using const");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("formatDiagnostic: formats hint", () => {
|
|
150
|
+
const diag = {
|
|
151
|
+
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } },
|
|
152
|
+
message: "Prefer arrow function",
|
|
153
|
+
severity: 4,
|
|
154
|
+
};
|
|
155
|
+
const result = formatDiagnostic(diag as any);
|
|
156
|
+
assertEqual(result, "HINT [1:1] Prefer arrow function");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// filterDiagnosticsBySeverity tests
|
|
161
|
+
// ============================================================================
|
|
162
|
+
|
|
163
|
+
test("filterDiagnosticsBySeverity: all returns everything", () => {
|
|
164
|
+
const diags = [
|
|
165
|
+
{ severity: 1, message: "error", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
|
|
166
|
+
{ severity: 2, message: "warning", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
|
|
167
|
+
{ severity: 3, message: "info", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
|
|
168
|
+
{ severity: 4, message: "hint", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
|
|
169
|
+
];
|
|
170
|
+
const result = filterDiagnosticsBySeverity(diags as any, "all");
|
|
171
|
+
assertEqual(result.length, 4);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("filterDiagnosticsBySeverity: error returns only errors", () => {
|
|
175
|
+
const diags = [
|
|
176
|
+
{ severity: 1, message: "error", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
|
|
177
|
+
{ severity: 2, message: "warning", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
|
|
178
|
+
];
|
|
179
|
+
const result = filterDiagnosticsBySeverity(diags as any, "error");
|
|
180
|
+
assertEqual(result.length, 1);
|
|
181
|
+
assertEqual(result[0].message, "error");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("filterDiagnosticsBySeverity: warning returns errors and warnings", () => {
|
|
185
|
+
const diags = [
|
|
186
|
+
{ severity: 1, message: "error", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
|
|
187
|
+
{ severity: 2, message: "warning", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
|
|
188
|
+
{ severity: 3, message: "info", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
|
|
189
|
+
];
|
|
190
|
+
const result = filterDiagnosticsBySeverity(diags as any, "warning");
|
|
191
|
+
assertEqual(result.length, 2);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("filterDiagnosticsBySeverity: info returns errors, warnings, and info", () => {
|
|
195
|
+
const diags = [
|
|
196
|
+
{ severity: 1, message: "error", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
|
|
197
|
+
{ severity: 2, message: "warning", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
|
|
198
|
+
{ severity: 3, message: "info", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
|
|
199
|
+
{ severity: 4, message: "hint", range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } },
|
|
200
|
+
];
|
|
201
|
+
const result = filterDiagnosticsBySeverity(diags as any, "info");
|
|
202
|
+
assertEqual(result.length, 3);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// ============================================================================
|
|
206
|
+
// Run tests
|
|
207
|
+
// ============================================================================
|
|
208
|
+
|
|
209
|
+
async function runTests(): Promise<void> {
|
|
210
|
+
console.log("Running index.ts unit tests...\n");
|
|
211
|
+
|
|
212
|
+
let passed = 0;
|
|
213
|
+
let failed = 0;
|
|
214
|
+
|
|
215
|
+
for (const { name, fn } of tests) {
|
|
216
|
+
try {
|
|
217
|
+
await fn();
|
|
218
|
+
console.log(` ${name}... ✓`);
|
|
219
|
+
passed++;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
222
|
+
console.log(` ${name}... ✗`);
|
|
223
|
+
console.log(` Error: ${msg}\n`);
|
|
224
|
+
failed++;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
229
|
+
|
|
230
|
+
if (failed > 0) {
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
runTests();
|