@dreki-gg/pi-lsp 0.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/README.md +131 -0
- package/extensions/lsp/client.ts +439 -0
- package/extensions/lsp/config.ts +181 -0
- package/extensions/lsp/formatting.ts +312 -0
- package/extensions/lsp/index.ts +159 -0
- package/extensions/lsp/protocol.ts +208 -0
- package/extensions/lsp/tools.ts +291 -0
- package/extensions/lsp/types.ts +243 -0
- package/package.json +40 -0
- package/test/client.test.ts +103 -0
- package/test/config.test.ts +121 -0
- package/test/formatting.test.ts +193 -0
- package/test/index.test.ts +188 -0
- package/test/mock-lsp-server.ts +294 -0
- package/test/tools.test.ts +218 -0
- package/test/typescript.integration.test.ts +158 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from 'bun:test';
|
|
2
|
+
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
import { LspClient } from '../extensions/lsp/client';
|
|
8
|
+
|
|
9
|
+
const cleanup: string[] = [];
|
|
10
|
+
|
|
11
|
+
async function makeFixtureWorkspace() {
|
|
12
|
+
const dir = await mkdtemp(join(tmpdir(), 'pi-lsp-ts-fixture-'));
|
|
13
|
+
cleanup.push(dir);
|
|
14
|
+
await mkdir(join(dir, 'src'), { recursive: true });
|
|
15
|
+
|
|
16
|
+
await writeFile(
|
|
17
|
+
join(dir, 'tsconfig.json'),
|
|
18
|
+
JSON.stringify(
|
|
19
|
+
{
|
|
20
|
+
compilerOptions: {
|
|
21
|
+
target: 'ES2022',
|
|
22
|
+
module: 'ESNext',
|
|
23
|
+
moduleResolution: 'Bundler',
|
|
24
|
+
strict: true,
|
|
25
|
+
noEmit: true,
|
|
26
|
+
skipLibCheck: true,
|
|
27
|
+
},
|
|
28
|
+
include: ['src/**/*.ts'],
|
|
29
|
+
},
|
|
30
|
+
null,
|
|
31
|
+
2,
|
|
32
|
+
),
|
|
33
|
+
'utf8',
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
await writeFile(
|
|
37
|
+
join(dir, 'src', 'shapes.ts'),
|
|
38
|
+
[
|
|
39
|
+
'export interface Shape {',
|
|
40
|
+
' area(): number;',
|
|
41
|
+
'}',
|
|
42
|
+
'',
|
|
43
|
+
'export class Circle implements Shape {',
|
|
44
|
+
' constructor(private radius: number) {}',
|
|
45
|
+
'',
|
|
46
|
+
' area(): number {',
|
|
47
|
+
' return Math.PI * this.radius * this.radius;',
|
|
48
|
+
' }',
|
|
49
|
+
'}',
|
|
50
|
+
'',
|
|
51
|
+
'export function useShape(shape: Shape): number {',
|
|
52
|
+
' return shape.area();',
|
|
53
|
+
'}',
|
|
54
|
+
'',
|
|
55
|
+
'export const broken: number = "oops";',
|
|
56
|
+
'',
|
|
57
|
+
].join('\n'),
|
|
58
|
+
'utf8',
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
await writeFile(
|
|
62
|
+
join(dir, 'src', 'main.ts'),
|
|
63
|
+
[
|
|
64
|
+
'import { Circle, useShape } from "./shapes";',
|
|
65
|
+
'',
|
|
66
|
+
'const circle = new Circle(2);',
|
|
67
|
+
'export const areaValue = useShape(circle);',
|
|
68
|
+
'',
|
|
69
|
+
].join('\n'),
|
|
70
|
+
'utf8',
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return dir;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
afterEach(async () => {
|
|
77
|
+
await Promise.all(cleanup.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('real TypeScript integration', () => {
|
|
81
|
+
test('typescript-language-server resolves diagnostics and navigation in a fixture workspace', async () => {
|
|
82
|
+
const workspace = await makeFixtureWorkspace();
|
|
83
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
84
|
+
const tsServerBin = join(packageRoot, 'node_modules', '.bin', 'typescript-language-server');
|
|
85
|
+
|
|
86
|
+
const client = new LspClient(
|
|
87
|
+
{
|
|
88
|
+
name: 'typescript',
|
|
89
|
+
command: tsServerBin,
|
|
90
|
+
args: ['--stdio'],
|
|
91
|
+
extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'],
|
|
92
|
+
env: {},
|
|
93
|
+
initializationOptions: {},
|
|
94
|
+
},
|
|
95
|
+
workspace,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await client.ensureInitialized();
|
|
100
|
+
|
|
101
|
+
const diagnostics = await client.getDiagnostics('src/shapes.ts');
|
|
102
|
+
expect(diagnostics.length).toBeGreaterThan(0);
|
|
103
|
+
expect(diagnostics.some((d) => d.message.toLowerCase().includes('string'))).toBe(true);
|
|
104
|
+
|
|
105
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
106
|
+
|
|
107
|
+
const hover = await client.hover('src/main.ts', { line: 2, character: 18 });
|
|
108
|
+
const hoverText =
|
|
109
|
+
hover && typeof hover.contents !== 'string' && 'value' in hover.contents
|
|
110
|
+
? hover.contents.value
|
|
111
|
+
: JSON.stringify(hover?.contents ?? '');
|
|
112
|
+
expect(hoverText.toLowerCase()).toContain('circle');
|
|
113
|
+
|
|
114
|
+
const defs = await client.definition('src/main.ts', { line: 3, character: 25 });
|
|
115
|
+
expect(defs.length).toBeGreaterThan(0);
|
|
116
|
+
expect(defs.some((d) => d.uri.endsWith('/src/shapes.ts'))).toBe(true);
|
|
117
|
+
|
|
118
|
+
const refs = await client.references('src/shapes.ts', { line: 12, character: 20 });
|
|
119
|
+
expect(refs.length).toBeGreaterThan(1);
|
|
120
|
+
expect(refs.some((r) => r.uri.endsWith('/src/main.ts'))).toBe(true);
|
|
121
|
+
|
|
122
|
+
const impls = await client.implementation('src/shapes.ts', { line: 0, character: 17 });
|
|
123
|
+
expect(impls.length).toBeGreaterThan(0);
|
|
124
|
+
expect(impls.some((i) => i.uri.endsWith('/src/shapes.ts'))).toBe(true);
|
|
125
|
+
|
|
126
|
+
const docSymbols = await client.documentSymbol('src/shapes.ts');
|
|
127
|
+
expect(docSymbols.length).toBeGreaterThan(0);
|
|
128
|
+
|
|
129
|
+
const workspaceSymbols = await client.workspaceSymbol('Circle');
|
|
130
|
+
expect(workspaceSymbols.length).toBeGreaterThan(0);
|
|
131
|
+
expect(workspaceSymbols.some((s) => s.name === 'Circle')).toBe(true);
|
|
132
|
+
|
|
133
|
+
const hierarchyItems = await client.prepareCallHierarchy('src/shapes.ts', {
|
|
134
|
+
line: 12,
|
|
135
|
+
character: 16,
|
|
136
|
+
});
|
|
137
|
+
expect(hierarchyItems.length).toBeGreaterThan(0);
|
|
138
|
+
|
|
139
|
+
const incoming = await client.incomingCalls(hierarchyItems[0]!);
|
|
140
|
+
expect(incoming.length).toBeGreaterThan(0);
|
|
141
|
+
|
|
142
|
+
const outgoing = await client.outgoingCalls(hierarchyItems[0]!);
|
|
143
|
+
expect(outgoing.length).toBeGreaterThan(0);
|
|
144
|
+
|
|
145
|
+
const codeActions = await client.codeActions(
|
|
146
|
+
'src/shapes.ts',
|
|
147
|
+
{
|
|
148
|
+
start: { line: 16, character: 0 },
|
|
149
|
+
end: { line: 16, character: 100 },
|
|
150
|
+
},
|
|
151
|
+
{ diagnostics },
|
|
152
|
+
);
|
|
153
|
+
expect(Array.isArray(codeActions)).toBe(true);
|
|
154
|
+
} finally {
|
|
155
|
+
await client.shutdown();
|
|
156
|
+
}
|
|
157
|
+
}, 30000);
|
|
158
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"incremental": true,
|
|
13
|
+
"tsBuildInfoFile": "./node_modules/.cache/lsp.tsbuildinfo",
|
|
14
|
+
"rootDir": ".",
|
|
15
|
+
"types": ["bun-types", "node"]
|
|
16
|
+
},
|
|
17
|
+
"include": ["extensions/**/*.ts", "test/**/*.ts"]
|
|
18
|
+
}
|