@ts-for-gir/language-server 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 +257 -0
- package/package.json +35 -0
- package/src/__tests__/validation.test.ts +192 -0
- package/src/index.ts +2 -0
- package/src/types/hover.ts +23 -0
- package/src/types/index.ts +3 -0
- package/src/types/type-expectation.ts +16 -0
- package/src/types/validation.ts +19 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/path.ts +34 -0
- package/src/utils/string.ts +48 -0
- package/src/utils/typescript.ts +161 -0
- package/src/validation.ts +303 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# @ts-for-gir/language-server
|
|
2
|
+
|
|
3
|
+
A TypeScript validation and hover utility for testing generated GIR type definitions.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- TypeScript code validation optimized for GJS/GIR types
|
|
8
|
+
- Essential compiler error detection
|
|
9
|
+
- Support for GIR type definitions and module resolution
|
|
10
|
+
- **Hover functionality** for type inspection at specific positions
|
|
11
|
+
- Automatic type discovery for GIR definitions
|
|
12
|
+
- Clean API for validation and type checking
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
yarn add @ts-for-gir/language-server
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic TypeScript Validation
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { validateTypeScript } from '@ts-for-gir/language-server';
|
|
26
|
+
|
|
27
|
+
const result = validateTypeScript(`
|
|
28
|
+
interface Test {
|
|
29
|
+
name: string;
|
|
30
|
+
}
|
|
31
|
+
const obj: Test = { name: "test" };
|
|
32
|
+
`);
|
|
33
|
+
|
|
34
|
+
console.log(result.success); // true or false
|
|
35
|
+
console.log(result.errors); // Array of error messages
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### GIR Type Validation
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { validateGIRTypeScript } from '@ts-for-gir/language-server';
|
|
42
|
+
import path from 'path';
|
|
43
|
+
|
|
44
|
+
const typesPath = path.resolve('./types/@types');
|
|
45
|
+
const result = validateGIRTypeScript(`
|
|
46
|
+
import Gtk from 'gi://Gtk?version=4.0';
|
|
47
|
+
|
|
48
|
+
const button = new Gtk.Button();
|
|
49
|
+
button.set_label("Hello World");
|
|
50
|
+
`, typesPath);
|
|
51
|
+
|
|
52
|
+
console.log(result.success); // true or false
|
|
53
|
+
console.log(result.errors); // Array of error messages
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Automatic GIR Type Discovery
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { validateGIRTypeScriptAuto } from '@ts-for-gir/language-server';
|
|
60
|
+
|
|
61
|
+
const result = validateGIRTypeScriptAuto(`
|
|
62
|
+
import Gtk from 'gi://Gtk?version=4.0';
|
|
63
|
+
|
|
64
|
+
const button = new Gtk.Button();
|
|
65
|
+
button.set_label("Hello World");
|
|
66
|
+
`);
|
|
67
|
+
|
|
68
|
+
console.log(result.success); // true or false
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Hover Functionality - Type Inspection
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { getIdentifierTypeAuto } from '@ts-for-gir/language-server';
|
|
75
|
+
|
|
76
|
+
const code = `
|
|
77
|
+
import Gtk from 'gi://Gtk?version=4.0';
|
|
78
|
+
|
|
79
|
+
const button = new Gtk.Button();
|
|
80
|
+
const window = new Gtk.Window();
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
// Get type information for the 'button' variable
|
|
84
|
+
const result = getIdentifierTypeAuto(code, 'button');
|
|
85
|
+
|
|
86
|
+
console.log(result.success); // true or false
|
|
87
|
+
console.log(result.type); // e.g., "Gtk.Button"
|
|
88
|
+
console.log(result.documentation); // JSDoc documentation if available
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Type Expectation - Validate Expected Types
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { expectIdentifierTypeAuto } from '@ts-for-gir/language-server';
|
|
95
|
+
|
|
96
|
+
const code = `
|
|
97
|
+
import Gtk from 'gi://Gtk?version=4.0';
|
|
98
|
+
|
|
99
|
+
const button = new Gtk.Button();
|
|
100
|
+
const window = new Gtk.Window();
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
// Validate that 'button' is of type 'Gtk.Button' (exact match)
|
|
104
|
+
const result = expectIdentifierTypeAuto(code, 'button', 'Gtk.Button');
|
|
105
|
+
|
|
106
|
+
console.log(result.success); // true or false
|
|
107
|
+
console.log(result.matches); // true if types match
|
|
108
|
+
console.log(result.actualType); // e.g., "Gtk.Button"
|
|
109
|
+
console.log(result.expectedType); // "Gtk.Button"
|
|
110
|
+
console.log(result.error); // Error message if types don't match
|
|
111
|
+
|
|
112
|
+
// Validate using RegExp pattern (flexible matching)
|
|
113
|
+
const patternResult = expectIdentifierTypeAuto(code, 'button', /Button|Gtk\.Button/);
|
|
114
|
+
console.log(patternResult.matches); // true if type matches pattern
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## API
|
|
118
|
+
|
|
119
|
+
### Validation Functions
|
|
120
|
+
|
|
121
|
+
#### `validateTypeScript(code: string, options?: ValidationOptions)`
|
|
122
|
+
|
|
123
|
+
Validates TypeScript code and returns validation results.
|
|
124
|
+
|
|
125
|
+
**Returns:** `ValidationResult`
|
|
126
|
+
- `success: boolean` - Whether validation passed
|
|
127
|
+
- `errors: string[]` - Array of error messages
|
|
128
|
+
- `warnings: string[]` - Array of warning messages
|
|
129
|
+
|
|
130
|
+
**Options:**
|
|
131
|
+
- `strict?: boolean` - Enable strict mode (default: true)
|
|
132
|
+
- `typesPath?: string` - Path to directory containing GIR type definitions
|
|
133
|
+
|
|
134
|
+
#### `validateGIRTypeScript(code: string, typesPath: string, options?: ValidationOptions)`
|
|
135
|
+
|
|
136
|
+
Validates GIR TypeScript code with type definitions.
|
|
137
|
+
|
|
138
|
+
#### `validateGIRTypeScriptAuto(code: string, options?: ValidationOptions)`
|
|
139
|
+
|
|
140
|
+
Validates GIR TypeScript code with automatic type discovery.
|
|
141
|
+
|
|
142
|
+
### Hover Functions
|
|
143
|
+
|
|
144
|
+
#### `getIdentifierType(code: string, identifier: string, options?: HoverOptions)`
|
|
145
|
+
|
|
146
|
+
Gets type information for a specific identifier in TypeScript code.
|
|
147
|
+
|
|
148
|
+
**Returns:** `HoverResult`
|
|
149
|
+
- `success: boolean` - Whether type checking succeeded
|
|
150
|
+
- `type?: string` - The inferred type (e.g., "Gtk.Button", "string", "number")
|
|
151
|
+
- `documentation?: string` - JSDoc documentation if available
|
|
152
|
+
- `error?: string` - Error message if failed
|
|
153
|
+
|
|
154
|
+
#### `getIdentifierTypeAuto(code: string, identifier: string, options?: HoverOptions)`
|
|
155
|
+
|
|
156
|
+
Gets type information with automatic GIR type discovery.
|
|
157
|
+
|
|
158
|
+
### Type Expectation Functions
|
|
159
|
+
|
|
160
|
+
#### `expectIdentifierType(code: string, identifier: string, expectedType: string | RegExp, options?: HoverOptions)`
|
|
161
|
+
|
|
162
|
+
Validates that a specific identifier has the expected type.
|
|
163
|
+
|
|
164
|
+
**Returns:** `TypeExpectationResult`
|
|
165
|
+
- `success: boolean` - Whether type checking succeeded
|
|
166
|
+
- `matches: boolean` - Whether the actual type matches the expected type
|
|
167
|
+
- `actualType?: string` - The actual inferred type
|
|
168
|
+
- `expectedType: string | RegExp` - The expected type or pattern that was tested for
|
|
169
|
+
- `documentation?: string` - JSDoc documentation if available
|
|
170
|
+
- `error?: string` - Error message if validation failed
|
|
171
|
+
|
|
172
|
+
**Expected Type Patterns:**
|
|
173
|
+
- `string` - Exact type name match (e.g., "Gtk.Button")
|
|
174
|
+
- `RegExp` - Pattern matching (e.g., `/Variant.*\[\]|Array.*Variant/`)
|
|
175
|
+
|
|
176
|
+
#### `expectIdentifierTypeAuto(code: string, identifier: string, expectedType: string | RegExp, options?: HoverOptions)`
|
|
177
|
+
|
|
178
|
+
Validates that a specific identifier has the expected type with automatic GIR type discovery.
|
|
179
|
+
|
|
180
|
+
## Use Cases
|
|
181
|
+
|
|
182
|
+
### Testing GIR Type Definitions
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { expectIdentifierTypeAuto } from '@ts-for-gir/language-server';
|
|
186
|
+
|
|
187
|
+
// Test if Gtk.Button type is correctly inferred
|
|
188
|
+
const result = expectIdentifierTypeAuto(`
|
|
189
|
+
import Gtk from 'gi://Gtk?version=4.0';
|
|
190
|
+
const button = new Gtk.Button();
|
|
191
|
+
`, 'button', 'Gtk.Button');
|
|
192
|
+
|
|
193
|
+
// Should return: { success: true, matches: true, actualType: "Gtk.Button" }
|
|
194
|
+
console.log(result.matches); // true if correctly typed
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Verifying Complex Type Inference
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// Test complex type inference
|
|
201
|
+
const result = expectIdentifierTypeAuto(`
|
|
202
|
+
import Gtk from 'gi://Gtk?version=4.0';
|
|
203
|
+
|
|
204
|
+
const box = new Gtk.Box(Gtk.Orientation.VERTICAL, 10);
|
|
205
|
+
const label = new Gtk.Label("Hello");
|
|
206
|
+
|
|
207
|
+
box.append(label);
|
|
208
|
+
`, 'box', 'Gtk.Box');
|
|
209
|
+
|
|
210
|
+
// Verify the type is correctly inferred
|
|
211
|
+
console.log(result.matches); // Should be true
|
|
212
|
+
console.log(result.actualType); // Should be "Gtk.Box"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Unit Testing Type Definitions
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import { expectIdentifierTypeAuto } from '@ts-for-gir/language-server';
|
|
219
|
+
|
|
220
|
+
describe('GIR Type Tests', () => {
|
|
221
|
+
test('Gtk.Button should be correctly typed', () => {
|
|
222
|
+
const result = expectIdentifierTypeAuto(`
|
|
223
|
+
import Gtk from 'gi://Gtk?version=4.0';
|
|
224
|
+
const button = new Gtk.Button();
|
|
225
|
+
`, 'button', 'Gtk.Button');
|
|
226
|
+
|
|
227
|
+
expect(result.success).toBe(true);
|
|
228
|
+
expect(result.matches).toBe(true);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('GVariant unpack methods should return correct types', () => {
|
|
232
|
+
const result = expectIdentifierTypeAuto(`
|
|
233
|
+
import GLib from 'gi://GLib?version=2.0';
|
|
234
|
+
const variant = new GLib.Variant("as", ["one", "two"]);
|
|
235
|
+
const unpacked = variant.unpack();
|
|
236
|
+
`, 'unpacked', /Variant.*\[\]|Array.*Variant/);
|
|
237
|
+
|
|
238
|
+
expect(result.success).toBe(true);
|
|
239
|
+
expect(result.matches).toBe(true);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('Complex type patterns should work', () => {
|
|
243
|
+
const result = expectIdentifierTypeAuto(`
|
|
244
|
+
import GLib from 'gi://GLib?version=2.0';
|
|
245
|
+
const variant = new GLib.Variant("(si)", ["hello", 42]);
|
|
246
|
+
const deepUnpack = variant.deepUnpack();
|
|
247
|
+
`, 'deepUnpack', /\[string,\s*number\]/);
|
|
248
|
+
|
|
249
|
+
expect(result.success).toBe(true);
|
|
250
|
+
expect(result.matches).toBe(true);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Development
|
|
256
|
+
|
|
257
|
+
This package provides the foundation for building GIR type validation and inspection tools. The type expectation functionality enables precise testing of whether generated TypeScript definitions match the expected types, making it ideal for automated testing and validation of GIR type generation.
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ts-for-gir/language-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript Language Server utilities for validating generated GIR type definitions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"typescript",
|
|
12
|
+
"language-server",
|
|
13
|
+
"gir",
|
|
14
|
+
"gobject",
|
|
15
|
+
"introspection",
|
|
16
|
+
"validation"
|
|
17
|
+
],
|
|
18
|
+
"author": "ts-for-gir contributors",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=22"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@ts-for-gir/lib": "^4.0.0-beta.26",
|
|
25
|
+
"typescript": "^5.9.2"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^24.2.1",
|
|
33
|
+
"vitest": "^3.2.4"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basic validation tests for language-server utilities
|
|
3
|
+
* Tests the core functionality of TypeScript validation and hover features
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { expectIdentifierType, getIdentifierType, validateTypeScript } from "../index.ts";
|
|
8
|
+
|
|
9
|
+
describe("Language Server Validation", () => {
|
|
10
|
+
describe("validateTypeScript", () => {
|
|
11
|
+
it("should validate correct TypeScript code", () => {
|
|
12
|
+
const code = `
|
|
13
|
+
interface User {
|
|
14
|
+
name: string;
|
|
15
|
+
age: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const user: User = {
|
|
19
|
+
name: "John",
|
|
20
|
+
age: 30
|
|
21
|
+
};
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const result = validateTypeScript(code);
|
|
25
|
+
expect(result.success).toBe(true);
|
|
26
|
+
expect(result.errors).toHaveLength(0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should detect type errors", () => {
|
|
30
|
+
const code = `
|
|
31
|
+
const num: number = "not a number";
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
const result = validateTypeScript(code);
|
|
35
|
+
expect(result.success).toBe(false);
|
|
36
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
37
|
+
expect(result.errors[0]).toContain("Type 'string' is not assignable to type 'number'");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should detect missing properties", () => {
|
|
41
|
+
const code = `
|
|
42
|
+
interface Config {
|
|
43
|
+
host: string;
|
|
44
|
+
port: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const config: Config = {
|
|
48
|
+
host: "localhost"
|
|
49
|
+
// missing port
|
|
50
|
+
};
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
const result = validateTypeScript(code);
|
|
54
|
+
expect(result.success).toBe(false);
|
|
55
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("getIdentifierType", () => {
|
|
60
|
+
it("should return type for simple variables", () => {
|
|
61
|
+
const code = `
|
|
62
|
+
const message: string = "Hello World";
|
|
63
|
+
const count: number = 42;
|
|
64
|
+
const isActive: boolean = true;
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const messageType = getIdentifierType(code, "message");
|
|
68
|
+
expect(messageType.success).toBe(true);
|
|
69
|
+
expect(messageType.type).toContain("string");
|
|
70
|
+
|
|
71
|
+
const countType = getIdentifierType(code, "count");
|
|
72
|
+
expect(countType.success).toBe(true);
|
|
73
|
+
expect(countType.type).toContain("number");
|
|
74
|
+
|
|
75
|
+
const activeType = getIdentifierType(code, "isActive");
|
|
76
|
+
expect(activeType.success).toBe(true);
|
|
77
|
+
expect(activeType.type).toContain("boolean");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should return type for interface instances", () => {
|
|
81
|
+
const code = `
|
|
82
|
+
interface Product {
|
|
83
|
+
id: string;
|
|
84
|
+
name: string;
|
|
85
|
+
price: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const product: Product = {
|
|
89
|
+
id: "123",
|
|
90
|
+
name: "Laptop",
|
|
91
|
+
price: 999.99
|
|
92
|
+
};
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
const result = getIdentifierType(code, "product");
|
|
96
|
+
expect(result.success).toBe(true);
|
|
97
|
+
expect(result.type).toContain("Product");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should return type for function signatures", () => {
|
|
101
|
+
const code = `
|
|
102
|
+
function add(a: number, b: number): number {
|
|
103
|
+
return a + b;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const multiply = (x: number, y: number): number => x * y;
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
const addType = getIdentifierType(code, "add");
|
|
110
|
+
expect(addType.success).toBe(true);
|
|
111
|
+
expect(addType.type).toMatch(/function|=>/);
|
|
112
|
+
expect(addType.type).toContain("number");
|
|
113
|
+
|
|
114
|
+
const multiplyType = getIdentifierType(code, "multiply");
|
|
115
|
+
expect(multiplyType.success).toBe(true);
|
|
116
|
+
expect(multiplyType.type).toMatch(/=>/);
|
|
117
|
+
expect(multiplyType.type).toContain("number");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should handle non-existent identifiers", () => {
|
|
121
|
+
const code = `
|
|
122
|
+
const existing = "value";
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
const result = getIdentifierType(code, "nonExistent");
|
|
126
|
+
expect(result.success).toBe(false);
|
|
127
|
+
expect(result.error).toContain("not found");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("expectIdentifierType", () => {
|
|
132
|
+
it("should match exact types", () => {
|
|
133
|
+
const code = `
|
|
134
|
+
const text: string = "Hello";
|
|
135
|
+
const num: number = 42;
|
|
136
|
+
`;
|
|
137
|
+
|
|
138
|
+
const textResult = expectIdentifierType(code, "text", "string");
|
|
139
|
+
expect(textResult.success).toBe(true);
|
|
140
|
+
expect(textResult.matches).toBe(true);
|
|
141
|
+
|
|
142
|
+
const numResult = expectIdentifierType(code, "num", "number");
|
|
143
|
+
expect(numResult.success).toBe(true);
|
|
144
|
+
expect(numResult.matches).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should match with regex patterns", () => {
|
|
148
|
+
const code = `
|
|
149
|
+
const arr: string[] = ["one", "two"];
|
|
150
|
+
const tuple: [string, number] = ["hello", 42];
|
|
151
|
+
`;
|
|
152
|
+
|
|
153
|
+
const arrResult = expectIdentifierType(code, "arr", /string.*\[\]/);
|
|
154
|
+
expect(arrResult.success).toBe(true);
|
|
155
|
+
expect(arrResult.matches).toBe(true);
|
|
156
|
+
|
|
157
|
+
const tupleResult = expectIdentifierType(code, "tuple", /\[string,\s*number\]/);
|
|
158
|
+
expect(tupleResult.success).toBe(true);
|
|
159
|
+
expect(tupleResult.matches).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should detect type mismatches", () => {
|
|
163
|
+
const code = `
|
|
164
|
+
const value: string = "text";
|
|
165
|
+
`;
|
|
166
|
+
|
|
167
|
+
const result = expectIdentifierType(code, "value", "number");
|
|
168
|
+
expect(result.success).toBe(true);
|
|
169
|
+
expect(result.matches).toBe(false);
|
|
170
|
+
expect(result.actualType).toContain("string");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should handle complex types", () => {
|
|
174
|
+
const code = `
|
|
175
|
+
type Status = 'pending' | 'success' | 'error';
|
|
176
|
+
interface Response<T> {
|
|
177
|
+
data: T;
|
|
178
|
+
status: Status;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const response: Response<string> = {
|
|
182
|
+
data: "result",
|
|
183
|
+
status: 'success'
|
|
184
|
+
};
|
|
185
|
+
`;
|
|
186
|
+
|
|
187
|
+
const result = expectIdentifierType(code, "response", /Response<string>/);
|
|
188
|
+
expect(result.success).toBe(true);
|
|
189
|
+
expect(result.matches).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of a hover operation
|
|
3
|
+
*/
|
|
4
|
+
export interface HoverResult {
|
|
5
|
+
success: boolean;
|
|
6
|
+
/** The inferred type at the position */
|
|
7
|
+
type?: string;
|
|
8
|
+
/** Documentation or additional information */
|
|
9
|
+
documentation?: string;
|
|
10
|
+
/** Error message if hover failed */
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Options for hover operations
|
|
16
|
+
*/
|
|
17
|
+
export interface HoverOptions {
|
|
18
|
+
strict?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Path to the directory containing GIR type definitions (@types folder)
|
|
21
|
+
*/
|
|
22
|
+
typesPath?: string;
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of a type expectation validation
|
|
3
|
+
*/
|
|
4
|
+
export interface TypeExpectationResult {
|
|
5
|
+
success: boolean;
|
|
6
|
+
/** Whether the actual type matches the expected type */
|
|
7
|
+
matches: boolean;
|
|
8
|
+
/** The actual inferred type */
|
|
9
|
+
actualType?: string;
|
|
10
|
+
/** The expected type or pattern */
|
|
11
|
+
expectedType: string | RegExp;
|
|
12
|
+
/** Documentation or additional information */
|
|
13
|
+
documentation?: string;
|
|
14
|
+
/** Error message if validation failed */
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of a TypeScript validation operation
|
|
3
|
+
*/
|
|
4
|
+
export interface ValidationResult {
|
|
5
|
+
success: boolean;
|
|
6
|
+
errors: string[];
|
|
7
|
+
warnings: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Options for TypeScript validation operations
|
|
12
|
+
*/
|
|
13
|
+
export interface ValidationOptions {
|
|
14
|
+
strict?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Path to the directory containing GIR type definitions (@types folder)
|
|
17
|
+
*/
|
|
18
|
+
typesPath?: string;
|
|
19
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Common paths where GIR types might be located
|
|
6
|
+
*/
|
|
7
|
+
export const COMMON_TYPES_PATHS = [
|
|
8
|
+
path.resolve("./@types"),
|
|
9
|
+
path.resolve("../@types"),
|
|
10
|
+
path.resolve("../../@types"),
|
|
11
|
+
path.resolve("./types"),
|
|
12
|
+
path.resolve("../types"),
|
|
13
|
+
path.resolve("../../types"),
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Find the first available types directory that contains .d.ts files
|
|
18
|
+
*/
|
|
19
|
+
export function findTypesPath(): string | undefined {
|
|
20
|
+
for (const possiblePath of COMMON_TYPES_PATHS) {
|
|
21
|
+
try {
|
|
22
|
+
if (fs.existsSync(possiblePath)) {
|
|
23
|
+
// Check if it contains .d.ts files
|
|
24
|
+
const files = fs.readdirSync(possiblePath);
|
|
25
|
+
if (files.some((file) => file.endsWith(".d.ts"))) {
|
|
26
|
+
return possiblePath;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// Continue to next path
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper function to normalize type strings for comparison
|
|
3
|
+
*/
|
|
4
|
+
export function normalizeTypeString(typeString: string): string {
|
|
5
|
+
// Remove extra whitespace and normalize common type variations
|
|
6
|
+
return (
|
|
7
|
+
typeString
|
|
8
|
+
.trim()
|
|
9
|
+
.replace(/\s+/g, " ")
|
|
10
|
+
// Normalize union types (remove spaces around |)
|
|
11
|
+
.replace(/\s*\|\s*/g, "|")
|
|
12
|
+
// Normalize array types
|
|
13
|
+
.replace(/Array<(.+)>/g, "$1[]")
|
|
14
|
+
// Remove import() type wrappers for comparison
|
|
15
|
+
.replace(/import\(".*?"\)\./g, "")
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if two type strings match, considering type aliases and normalization
|
|
21
|
+
*/
|
|
22
|
+
export function typesMatch(actual: string, expected: string | RegExp): boolean {
|
|
23
|
+
// If expected is a RegExp, test against the actual type
|
|
24
|
+
if (expected instanceof RegExp) {
|
|
25
|
+
return expected.test(actual);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const normalizedActual = normalizeTypeString(actual);
|
|
29
|
+
const normalizedExpected = normalizeTypeString(expected);
|
|
30
|
+
|
|
31
|
+
// Direct match
|
|
32
|
+
if (normalizedActual === normalizedExpected) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check for common type aliases
|
|
37
|
+
const typeAliases = new Map([
|
|
38
|
+
["number", "Number"],
|
|
39
|
+
["string", "String"],
|
|
40
|
+
["boolean", "Boolean"],
|
|
41
|
+
["object", "Object"],
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
// Check both directions of type aliases
|
|
45
|
+
return (
|
|
46
|
+
typeAliases.get(normalizedActual) === normalizedExpected || typeAliases.get(normalizedExpected) === normalizedActual
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as ts from "typescript";
|
|
4
|
+
import type { HoverOptions } from "../types/index.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create TypeScript compiler options optimized for GJS/GIR types
|
|
8
|
+
*/
|
|
9
|
+
export function createCompilerOptions(strict?: boolean): ts.CompilerOptions {
|
|
10
|
+
return {
|
|
11
|
+
target: ts.ScriptTarget.ESNext,
|
|
12
|
+
module: ts.ModuleKind.ESNext,
|
|
13
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
14
|
+
skipLibCheck: true,
|
|
15
|
+
strict: strict ?? true,
|
|
16
|
+
noImplicitAny: true,
|
|
17
|
+
strictNullChecks: true,
|
|
18
|
+
noImplicitThis: true,
|
|
19
|
+
alwaysStrict: true,
|
|
20
|
+
noEmit: true,
|
|
21
|
+
allowSyntheticDefaultImports: true,
|
|
22
|
+
esModuleInterop: true,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create a virtual file system for type definitions
|
|
28
|
+
*/
|
|
29
|
+
export function createVirtualFileSystem(typesPath?: string): Map<string, string> {
|
|
30
|
+
const virtualFileSystem = new Map<string, string>();
|
|
31
|
+
|
|
32
|
+
if (!typesPath) {
|
|
33
|
+
return virtualFileSystem;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const files = fs.readdirSync(typesPath);
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
if (file.endsWith(".d.ts")) {
|
|
40
|
+
const filePath = path.resolve(typesPath, file);
|
|
41
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
42
|
+
// Store with full path for primary access
|
|
43
|
+
virtualFileSystem.set(filePath, content);
|
|
44
|
+
// Also store with relative path for triple-slash references
|
|
45
|
+
virtualFileSystem.set(`./${file}`, content);
|
|
46
|
+
virtualFileSystem.set(`./types/${file}`, content);
|
|
47
|
+
virtualFileSystem.set(`@types/${file}`, content);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
// Types path not accessible, continue without types
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return virtualFileSystem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create a TypeScript compiler host with virtual file system support
|
|
59
|
+
*/
|
|
60
|
+
export function createCompilerHost(
|
|
61
|
+
compilerOptions: ts.CompilerOptions,
|
|
62
|
+
sourceFile: ts.SourceFile,
|
|
63
|
+
virtualFileSystem: Map<string, string>,
|
|
64
|
+
fileName: string,
|
|
65
|
+
): ts.CompilerHost {
|
|
66
|
+
const host = ts.createCompilerHost(compilerOptions);
|
|
67
|
+
|
|
68
|
+
// Override file resolution to use virtual file system
|
|
69
|
+
const originalGetSourceFile = host.getSourceFile;
|
|
70
|
+
host.getSourceFile = (name, languageVersion) => {
|
|
71
|
+
if (name === fileName) {
|
|
72
|
+
return sourceFile;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check virtual file system first
|
|
76
|
+
if (virtualFileSystem.has(name)) {
|
|
77
|
+
const content = virtualFileSystem.get(name);
|
|
78
|
+
if (content !== undefined) {
|
|
79
|
+
return ts.createSourceFile(name, content, languageVersion, true);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check for relative path references
|
|
84
|
+
const baseName = path.basename(name);
|
|
85
|
+
if (virtualFileSystem.has(baseName)) {
|
|
86
|
+
const content = virtualFileSystem.get(baseName);
|
|
87
|
+
if (content !== undefined) {
|
|
88
|
+
return ts.createSourceFile(name, content, languageVersion, true);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return originalGetSourceFile.call(host, name, languageVersion);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return host;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create a TypeScript program with virtual file system support
|
|
100
|
+
*/
|
|
101
|
+
export function createTypeScriptProgram(code: string, options: HoverOptions = {}) {
|
|
102
|
+
const fileName = "test.ts";
|
|
103
|
+
const sourceFile = ts.createSourceFile(fileName, code, ts.ScriptTarget.ESNext, true);
|
|
104
|
+
|
|
105
|
+
const compilerOptions = createCompilerOptions(options.strict);
|
|
106
|
+
const virtualFileSystem = createVirtualFileSystem(options.typesPath);
|
|
107
|
+
const host = createCompilerHost(compilerOptions, sourceFile, virtualFileSystem, fileName);
|
|
108
|
+
|
|
109
|
+
// Files to include in the program
|
|
110
|
+
const filesToInclude = [fileName];
|
|
111
|
+
|
|
112
|
+
// Include index.d.ts if available
|
|
113
|
+
if (options.typesPath) {
|
|
114
|
+
const indexPath = path.resolve(options.typesPath, "index.d.ts");
|
|
115
|
+
if (virtualFileSystem.has(indexPath)) {
|
|
116
|
+
filesToInclude.push(indexPath);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const program = ts.createProgram(filesToInclude, compilerOptions, host);
|
|
121
|
+
|
|
122
|
+
return { program, sourceFile };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Find the position of a variable or identifier in TypeScript code
|
|
127
|
+
*/
|
|
128
|
+
export function findIdentifierPosition(sourceFile: ts.SourceFile, identifier: string): number | undefined {
|
|
129
|
+
let position: number | undefined;
|
|
130
|
+
|
|
131
|
+
function visit(node: ts.Node): void {
|
|
132
|
+
if (ts.isIdentifier(node) && node.text === identifier) {
|
|
133
|
+
// Prefer variable declarations over references
|
|
134
|
+
if (ts.isVariableDeclaration(node.parent) && node.parent.name === node) {
|
|
135
|
+
position = node.getStart();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Fall back to any identifier if no declaration found
|
|
139
|
+
if (position === undefined) {
|
|
140
|
+
position = node.getStart();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
ts.forEachChild(node, visit);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
visit(sourceFile);
|
|
147
|
+
return position;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get the node at a specific position in the source file
|
|
152
|
+
*/
|
|
153
|
+
export function getNodeAtPosition(sourceFile: ts.SourceFile, position: number): ts.Node | undefined {
|
|
154
|
+
function find(node: ts.Node): ts.Node | undefined {
|
|
155
|
+
if (position >= node.getStart() && position < node.getEnd()) {
|
|
156
|
+
return ts.forEachChild(node, find) || node;
|
|
157
|
+
}
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
return find(sourceFile);
|
|
161
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as ts from "typescript";
|
|
4
|
+
import type {
|
|
5
|
+
HoverOptions,
|
|
6
|
+
HoverResult,
|
|
7
|
+
TypeExpectationResult,
|
|
8
|
+
ValidationOptions,
|
|
9
|
+
ValidationResult,
|
|
10
|
+
} from "./types/index.ts";
|
|
11
|
+
import { findTypesPath } from "./utils/path.ts";
|
|
12
|
+
import { typesMatch } from "./utils/string.ts";
|
|
13
|
+
import { createTypeScriptProgram, findIdentifierPosition, getNodeAtPosition } from "./utils/typescript.ts";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* TypeScript validation for generated GIR types
|
|
17
|
+
*/
|
|
18
|
+
export function validateTypeScript(code: string, options: ValidationOptions = {}): ValidationResult {
|
|
19
|
+
try {
|
|
20
|
+
// Compiler options optimized for GJS/GIR types
|
|
21
|
+
const compilerOptions: ts.CompilerOptions = {
|
|
22
|
+
target: ts.ScriptTarget.ESNext,
|
|
23
|
+
module: ts.ModuleKind.ESNext,
|
|
24
|
+
// Don't specify lib to let TypeScript use defaults for target
|
|
25
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
26
|
+
skipLibCheck: true, // Skip lib checking to avoid path resolution issues
|
|
27
|
+
strict: options.strict ?? true,
|
|
28
|
+
noImplicitAny: true,
|
|
29
|
+
strictNullChecks: true,
|
|
30
|
+
noImplicitThis: true,
|
|
31
|
+
alwaysStrict: true,
|
|
32
|
+
noEmit: true,
|
|
33
|
+
// Allow module resolution for GIR modules
|
|
34
|
+
allowSyntheticDefaultImports: true,
|
|
35
|
+
esModuleInterop: true,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Create source file
|
|
39
|
+
const fileName = "test.ts";
|
|
40
|
+
const sourceFile = ts.createSourceFile(fileName, code, ts.ScriptTarget.ESNext, true);
|
|
41
|
+
|
|
42
|
+
// Files to include in the program
|
|
43
|
+
const filesToInclude = [fileName];
|
|
44
|
+
|
|
45
|
+
// Create virtual file system for type definitions
|
|
46
|
+
const virtualFileSystem = new Map<string, string>();
|
|
47
|
+
|
|
48
|
+
// Load all type definition files if types path is provided
|
|
49
|
+
if (options.typesPath) {
|
|
50
|
+
try {
|
|
51
|
+
const files = fs.readdirSync(options.typesPath);
|
|
52
|
+
for (const file of files) {
|
|
53
|
+
if (file.endsWith(".d.ts")) {
|
|
54
|
+
const filePath = path.resolve(options.typesPath, file);
|
|
55
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
56
|
+
// Store with full path for primary access
|
|
57
|
+
virtualFileSystem.set(filePath, content);
|
|
58
|
+
// Also store with relative path for triple-slash references
|
|
59
|
+
virtualFileSystem.set(`./${file}`, content);
|
|
60
|
+
virtualFileSystem.set(`./types/${file}`, content);
|
|
61
|
+
virtualFileSystem.set(`@types/${file}`, content);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// Types path not accessible, continue without types
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Include index.d.ts if available
|
|
70
|
+
if (options.typesPath) {
|
|
71
|
+
const indexPath = path.resolve(options.typesPath, "index.d.ts");
|
|
72
|
+
if (virtualFileSystem.has(indexPath)) {
|
|
73
|
+
filesToInclude.push(indexPath);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Create compiler host
|
|
78
|
+
const host = ts.createCompilerHost(compilerOptions);
|
|
79
|
+
|
|
80
|
+
// Override file resolution to use virtual file system
|
|
81
|
+
const originalGetSourceFile = host.getSourceFile;
|
|
82
|
+
host.getSourceFile = (name, languageVersion) => {
|
|
83
|
+
if (name === fileName) {
|
|
84
|
+
return sourceFile;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check virtual file system first
|
|
88
|
+
if (virtualFileSystem.has(name)) {
|
|
89
|
+
const content = virtualFileSystem.get(name);
|
|
90
|
+
if (content !== undefined) {
|
|
91
|
+
return ts.createSourceFile(name, content, languageVersion, true);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check for relative path references
|
|
96
|
+
const baseName = path.basename(name);
|
|
97
|
+
if (virtualFileSystem.has(baseName)) {
|
|
98
|
+
const content = virtualFileSystem.get(baseName);
|
|
99
|
+
if (content !== undefined) {
|
|
100
|
+
return ts.createSourceFile(name, content, languageVersion, true);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return originalGetSourceFile.call(host, name, languageVersion);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Create program and get diagnostics
|
|
108
|
+
const program = ts.createProgram(filesToInclude, compilerOptions, host);
|
|
109
|
+
const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile);
|
|
110
|
+
|
|
111
|
+
const errors: string[] = [];
|
|
112
|
+
const warnings: string[] = [];
|
|
113
|
+
|
|
114
|
+
for (const diagnostic of diagnostics) {
|
|
115
|
+
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
116
|
+
|
|
117
|
+
if (diagnostic.category === ts.DiagnosticCategory.Error) {
|
|
118
|
+
errors.push(message);
|
|
119
|
+
} else if (diagnostic.category === ts.DiagnosticCategory.Warning) {
|
|
120
|
+
warnings.push(message);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
success: errors.length === 0,
|
|
126
|
+
errors,
|
|
127
|
+
warnings,
|
|
128
|
+
};
|
|
129
|
+
} catch (error) {
|
|
130
|
+
return {
|
|
131
|
+
success: false,
|
|
132
|
+
errors: [`Validation failed: ${error}`],
|
|
133
|
+
warnings: [],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Validate GIR TypeScript code with type definitions
|
|
140
|
+
*/
|
|
141
|
+
export function validateGIRTypeScript(
|
|
142
|
+
code: string,
|
|
143
|
+
typesPath: string,
|
|
144
|
+
options: Omit<ValidationOptions, "typesPath"> = {},
|
|
145
|
+
): ValidationResult {
|
|
146
|
+
return validateTypeScript(code, {
|
|
147
|
+
...options,
|
|
148
|
+
typesPath,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Validate GIR TypeScript code with automatic type discovery
|
|
154
|
+
* Automatically finds and includes GIR type definitions from common locations
|
|
155
|
+
*/
|
|
156
|
+
export function validateGIRTypeScriptAuto(
|
|
157
|
+
code: string,
|
|
158
|
+
options: Omit<ValidationOptions, "typesPath"> = {},
|
|
159
|
+
): ValidationResult {
|
|
160
|
+
const typesPath = findTypesPath();
|
|
161
|
+
return validateTypeScript(code, {
|
|
162
|
+
...options,
|
|
163
|
+
typesPath,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get type information for a specific identifier in TypeScript code
|
|
169
|
+
*/
|
|
170
|
+
export function getIdentifierType(code: string, identifier: string, options: HoverOptions = {}): HoverResult {
|
|
171
|
+
try {
|
|
172
|
+
const { program, sourceFile } = createTypeScriptProgram(code, options);
|
|
173
|
+
|
|
174
|
+
// Find the position of the identifier
|
|
175
|
+
const position = findIdentifierPosition(sourceFile, identifier);
|
|
176
|
+
if (position === undefined) {
|
|
177
|
+
return {
|
|
178
|
+
success: false,
|
|
179
|
+
error: `Identifier '${identifier}' not found in code`,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Get type checker
|
|
184
|
+
const typeChecker = program.getTypeChecker();
|
|
185
|
+
|
|
186
|
+
// Find the node at the position
|
|
187
|
+
const node = getNodeAtPosition(sourceFile, position);
|
|
188
|
+
if (!node) {
|
|
189
|
+
return {
|
|
190
|
+
success: false,
|
|
191
|
+
error: `No node found at position ${position}`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Get the type of the node
|
|
196
|
+
const type = typeChecker.getTypeAtLocation(node);
|
|
197
|
+
const typeString = typeChecker.typeToString(type);
|
|
198
|
+
|
|
199
|
+
// Get symbol information for documentation
|
|
200
|
+
const symbol = typeChecker.getSymbolAtLocation(node);
|
|
201
|
+
const documentation = symbol
|
|
202
|
+
?.getDocumentationComment(typeChecker)
|
|
203
|
+
?.map((part) => part.text)
|
|
204
|
+
?.join("\n");
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
success: true,
|
|
208
|
+
type: typeString,
|
|
209
|
+
documentation: documentation || undefined,
|
|
210
|
+
};
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return {
|
|
213
|
+
success: false,
|
|
214
|
+
error: `Type checking failed: ${error}`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get type information for a specific identifier in TypeScript code with automatic type discovery
|
|
221
|
+
*/
|
|
222
|
+
export function getIdentifierTypeAuto(
|
|
223
|
+
code: string,
|
|
224
|
+
identifier: string,
|
|
225
|
+
options: Omit<HoverOptions, "typesPath"> = {},
|
|
226
|
+
): HoverResult {
|
|
227
|
+
const typesPath = findTypesPath();
|
|
228
|
+
return getIdentifierType(code, identifier, {
|
|
229
|
+
...options,
|
|
230
|
+
typesPath,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Validate that a specific identifier has the expected type
|
|
236
|
+
*/
|
|
237
|
+
export function expectIdentifierType(
|
|
238
|
+
code: string,
|
|
239
|
+
identifier: string,
|
|
240
|
+
expectedType: string | RegExp,
|
|
241
|
+
options: HoverOptions = {},
|
|
242
|
+
): TypeExpectationResult {
|
|
243
|
+
try {
|
|
244
|
+
// First get the actual type
|
|
245
|
+
const hoverResult = getIdentifierType(code, identifier, options);
|
|
246
|
+
|
|
247
|
+
if (!hoverResult.success) {
|
|
248
|
+
return {
|
|
249
|
+
success: false,
|
|
250
|
+
matches: false,
|
|
251
|
+
expectedType,
|
|
252
|
+
error: hoverResult.error,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const actualType = hoverResult.type;
|
|
257
|
+
if (!actualType) {
|
|
258
|
+
return {
|
|
259
|
+
success: false,
|
|
260
|
+
matches: false,
|
|
261
|
+
expectedType,
|
|
262
|
+
error: "No type information available",
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Check if types match
|
|
267
|
+
const matches = typesMatch(actualType, expectedType);
|
|
268
|
+
|
|
269
|
+
const expectedTypeDisplay = expectedType instanceof RegExp ? expectedType.toString() : expectedType;
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
success: true,
|
|
273
|
+
matches,
|
|
274
|
+
actualType,
|
|
275
|
+
expectedType,
|
|
276
|
+
documentation: hoverResult.documentation,
|
|
277
|
+
error: matches ? undefined : `Expected type '${expectedTypeDisplay}' but got '${actualType}'`,
|
|
278
|
+
};
|
|
279
|
+
} catch (error) {
|
|
280
|
+
return {
|
|
281
|
+
success: false,
|
|
282
|
+
matches: false,
|
|
283
|
+
expectedType,
|
|
284
|
+
error: `Type expectation validation failed: ${error}`,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Validate that a specific identifier has the expected type with automatic type discovery
|
|
291
|
+
*/
|
|
292
|
+
export function expectIdentifierTypeAuto(
|
|
293
|
+
code: string,
|
|
294
|
+
identifier: string,
|
|
295
|
+
expectedType: string | RegExp,
|
|
296
|
+
options: Omit<HoverOptions, "typesPath"> = {},
|
|
297
|
+
): TypeExpectationResult {
|
|
298
|
+
const typesPath = findTypesPath();
|
|
299
|
+
return expectIdentifierType(code, identifier, expectedType, {
|
|
300
|
+
...options,
|
|
301
|
+
typesPath,
|
|
302
|
+
});
|
|
303
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["ESNext"],
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"outDir": "lib",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noImplicitAny": false,
|
|
9
|
+
"strictNullChecks": true,
|
|
10
|
+
"noImplicitThis": true,
|
|
11
|
+
"alwaysStrict": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"allowImportingTsExtensions": true,
|
|
15
|
+
"verbatimModuleSyntax": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"rootDir": "src",
|
|
18
|
+
"resolveJsonModule": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"diagnostics": true,
|
|
21
|
+
"types": ["vitest/globals"]
|
|
22
|
+
},
|
|
23
|
+
"include": ["src/**/*.ts"],
|
|
24
|
+
"exclude": ["lib", "node_modules", "**/*.spec.ts"]
|
|
25
|
+
}
|
package/vitest.config.ts
ADDED