c-next 0.1.60 → 0.1.61
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 +8 -0
- package/package.json +1 -1
- package/src/cli/serve/ServeCommand.ts +144 -25
- package/src/cli/serve/__tests__/ServeCommand.test.ts +227 -3
- package/src/lib/__tests__/parseCHeader.test.ts +194 -0
- package/src/lib/parseCHeader.ts +114 -0
- package/src/lib/types/TSymbolKind.ts +2 -1
- package/src/transpiler/Transpiler.ts +145 -76
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +74 -46
- package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +103 -68
- package/src/transpiler/logic/analysis/NullCheckAnalyzer.ts +44 -35
- package/src/transpiler/logic/symbols/CSymbolCollector.ts +23 -16
- package/src/transpiler/logic/symbols/cnext/index.ts +47 -27
- package/src/transpiler/output/codegen/CodeGenerator.ts +348 -503
- package/src/transpiler/output/codegen/TypeResolver.ts +56 -32
- package/src/transpiler/output/codegen/TypeValidator.ts +24 -75
- package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +10 -7
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +38 -37
- package/src/transpiler/output/codegen/assignment/AssignmentContextBuilder.ts +96 -46
- package/src/transpiler/output/codegen/generators/__tests__/GeneratorRegistry.test.ts +51 -0
- package/src/transpiler/output/codegen/generators/statements/SwitchGenerator.ts +77 -40
- package/src/transpiler/output/codegen/generators/support/IncludeGenerator.ts +93 -47
- package/src/transpiler/output/codegen/helpers/AssignmentTargetExtractor.ts +86 -0
- package/src/transpiler/output/codegen/helpers/BaseIdentifierBuilder.ts +53 -0
- package/src/transpiler/output/codegen/helpers/CastValidator.ts +146 -0
- package/src/transpiler/output/codegen/helpers/ChildStatementCollector.ts +116 -0
- package/src/transpiler/output/codegen/helpers/LiteralEvaluator.ts +61 -0
- package/src/transpiler/output/codegen/helpers/PostfixChainBuilder.ts +101 -0
- package/src/transpiler/output/codegen/helpers/SimpleIdentifierResolver.ts +54 -0
- package/src/transpiler/output/codegen/helpers/StatementExpressionCollector.ts +117 -0
- package/src/transpiler/output/codegen/helpers/TransitiveModificationPropagator.ts +127 -0
- package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +249 -0
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentTargetExtractor.test.ts +105 -0
- package/src/transpiler/output/codegen/helpers/__tests__/BaseIdentifierBuilder.test.ts +68 -0
- package/src/transpiler/output/codegen/helpers/__tests__/CastValidator.test.ts +233 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ChildStatementCollector.test.ts +191 -0
- package/src/transpiler/output/codegen/helpers/__tests__/LiteralEvaluator.test.ts +62 -0
- package/src/transpiler/output/codegen/helpers/__tests__/PostfixChainBuilder.test.ts +147 -0
- package/src/transpiler/output/codegen/helpers/__tests__/SimpleIdentifierResolver.test.ts +105 -0
- package/src/transpiler/output/codegen/helpers/__tests__/StatementExpressionCollector.test.ts +221 -0
- package/src/transpiler/output/codegen/helpers/__tests__/TransitiveModificationPropagator.test.ts +289 -0
- package/src/transpiler/output/codegen/helpers/__tests__/TypeGenerationHelper.test.ts +369 -0
- package/src/transpiler/output/codegen/types/IBaseIdentifierResult.ts +11 -0
- package/src/transpiler/output/codegen/types/IPostfixChainDeps.ts +16 -0
- package/src/transpiler/output/codegen/types/IPostfixOperation.ts +11 -0
- package/src/transpiler/output/codegen/types/ISimpleIdentifierDeps.ts +21 -0
- package/src/lib/__tests__/transpiler.test.ts +0 -303
- package/src/lib/transpiler.ts +0 -149
- package/src/lib/types/ITranspileOptions.ts +0 -15
- package/src/lib/types/ITranspileResult.ts +0 -20
package/README.md
CHANGED
|
@@ -94,6 +94,14 @@ cnext src/ -o build/src --header-out build/include --clean
|
|
|
94
94
|
cnext --help
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
## VS Code Extension
|
|
98
|
+
|
|
99
|
+
The C-Next VS Code extension provides syntax highlighting, live C preview, IntelliSense, and error diagnostics.
|
|
100
|
+
|
|
101
|
+
**Install from:** [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=jlaustill.vscode-c-next) (coming soon)
|
|
102
|
+
|
|
103
|
+
**Source:** [github.com/jlaustill/vscode-c-next](https://github.com/jlaustill/vscode-c-next)
|
|
104
|
+
|
|
97
105
|
## Getting Started with PlatformIO
|
|
98
106
|
|
|
99
107
|
C-Next integrates seamlessly with PlatformIO embedded projects. The transpiler automatically converts `.cnx` files to `.c` before each build.
|
package/package.json
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ServeCommand
|
|
3
3
|
* JSON-RPC server for VS Code extension communication
|
|
4
|
+
*
|
|
5
|
+
* Phase 2b (ADR-060): Uses the full Transpiler for transpilation and symbol
|
|
6
|
+
* extraction, enabling include resolution, C++ auto-detection, and cross-file
|
|
7
|
+
* symbol support.
|
|
4
8
|
*/
|
|
5
9
|
|
|
6
10
|
import { createInterface, Interface } from "node:readline";
|
|
11
|
+
import { dirname } from "node:path";
|
|
7
12
|
import JsonRpcHandler from "./JsonRpcHandler";
|
|
8
13
|
import IJsonRpcRequest from "./types/IJsonRpcRequest";
|
|
9
14
|
import IJsonRpcResponse from "./types/IJsonRpcResponse";
|
|
10
15
|
import ConfigPrinter from "../ConfigPrinter";
|
|
11
|
-
import
|
|
16
|
+
import ConfigLoader from "../ConfigLoader";
|
|
17
|
+
import Transpiler from "../../transpiler/Transpiler";
|
|
12
18
|
import parseWithSymbols from "../../lib/parseWithSymbols";
|
|
19
|
+
import parseCHeader from "../../lib/parseCHeader";
|
|
13
20
|
|
|
14
21
|
/**
|
|
15
|
-
* Method handler type
|
|
22
|
+
* Method handler type (async to support Transpiler.transpileSource)
|
|
16
23
|
*/
|
|
17
|
-
type MethodHandler = (
|
|
24
|
+
type MethodHandler = (
|
|
25
|
+
params?: Record<string, unknown>,
|
|
26
|
+
) => Promise<IMethodResult>;
|
|
18
27
|
|
|
19
28
|
/**
|
|
20
29
|
* Result from a method handler
|
|
@@ -41,14 +50,17 @@ class ServeCommand {
|
|
|
41
50
|
private static shouldShutdown = false;
|
|
42
51
|
private static readline: Interface | null = null;
|
|
43
52
|
private static debugMode = false;
|
|
53
|
+
private static transpiler: Transpiler | null = null;
|
|
44
54
|
|
|
45
55
|
/**
|
|
46
56
|
* Method handlers registry
|
|
47
57
|
*/
|
|
48
58
|
private static readonly methods: Record<string, MethodHandler> = {
|
|
49
59
|
getVersion: ServeCommand.handleGetVersion,
|
|
60
|
+
initialize: ServeCommand.handleInitialize,
|
|
50
61
|
transpile: ServeCommand.handleTranspile,
|
|
51
62
|
parseSymbols: ServeCommand.handleParseSymbols,
|
|
63
|
+
parseCHeader: ServeCommand.handleParseCHeader,
|
|
52
64
|
shutdown: ServeCommand.handleShutdown,
|
|
53
65
|
};
|
|
54
66
|
|
|
@@ -115,20 +127,23 @@ class ServeCommand {
|
|
|
115
127
|
const request = parseResult.request!;
|
|
116
128
|
this.log(`method: ${request.method}`);
|
|
117
129
|
|
|
118
|
-
// Dispatch to method handler
|
|
119
|
-
|
|
120
|
-
|
|
130
|
+
// Dispatch to method handler (async)
|
|
131
|
+
this.dispatch(request).then((response) => {
|
|
132
|
+
this.writeResponse(response);
|
|
121
133
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
134
|
+
// Handle shutdown after response is written
|
|
135
|
+
if (this.shouldShutdown) {
|
|
136
|
+
this.readline?.close();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
126
139
|
}
|
|
127
140
|
|
|
128
141
|
/**
|
|
129
142
|
* Dispatch a request to the appropriate handler
|
|
130
143
|
*/
|
|
131
|
-
private static dispatch(
|
|
144
|
+
private static async dispatch(
|
|
145
|
+
request: IJsonRpcRequest,
|
|
146
|
+
): Promise<IJsonRpcResponse> {
|
|
132
147
|
const handler = this.methods[request.method];
|
|
133
148
|
|
|
134
149
|
if (!handler) {
|
|
@@ -139,7 +154,7 @@ class ServeCommand {
|
|
|
139
154
|
);
|
|
140
155
|
}
|
|
141
156
|
|
|
142
|
-
const result = handler(request.params);
|
|
157
|
+
const result = await handler(request.params);
|
|
143
158
|
|
|
144
159
|
if (result.success) {
|
|
145
160
|
return JsonRpcHandler.formatResponse(request.id, result.result);
|
|
@@ -162,19 +177,59 @@ class ServeCommand {
|
|
|
162
177
|
/**
|
|
163
178
|
* Handle getVersion method
|
|
164
179
|
*/
|
|
165
|
-
private static handleGetVersion(): IMethodResult {
|
|
180
|
+
private static async handleGetVersion(): Promise<IMethodResult> {
|
|
166
181
|
return {
|
|
167
182
|
success: true,
|
|
168
183
|
result: { version: ConfigPrinter.getVersion() },
|
|
169
184
|
};
|
|
170
185
|
}
|
|
171
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Handle initialize method
|
|
189
|
+
* Loads project config and creates a Transpiler instance
|
|
190
|
+
*/
|
|
191
|
+
private static async handleInitialize(
|
|
192
|
+
params?: Record<string, unknown>,
|
|
193
|
+
): Promise<IMethodResult> {
|
|
194
|
+
if (!params || typeof params.workspacePath !== "string") {
|
|
195
|
+
return {
|
|
196
|
+
success: false,
|
|
197
|
+
errorCode: JsonRpcHandler.ERROR_INVALID_PARAMS,
|
|
198
|
+
errorMessage: "Missing required param: workspacePath",
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const workspacePath = params.workspacePath;
|
|
203
|
+
ServeCommand.log(`initializing with workspace: ${workspacePath}`);
|
|
204
|
+
|
|
205
|
+
const config = ConfigLoader.load(workspacePath);
|
|
206
|
+
|
|
207
|
+
ServeCommand.transpiler = new Transpiler({
|
|
208
|
+
inputs: [],
|
|
209
|
+
includeDirs: config.include ?? [],
|
|
210
|
+
cppRequired: config.cppRequired ?? false,
|
|
211
|
+
target: config.target ?? "",
|
|
212
|
+
debugMode: config.debugMode ?? false,
|
|
213
|
+
noCache: config.noCache ?? false,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
ServeCommand.log(
|
|
217
|
+
`initialized (cppRequired=${config.cppRequired ?? false}, includeDirs=${(config.include ?? []).length})`,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
success: true,
|
|
222
|
+
result: { success: true },
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
172
226
|
/**
|
|
173
227
|
* Handle transpile method
|
|
228
|
+
* Uses full Transpiler for include resolution and C++ auto-detection
|
|
174
229
|
*/
|
|
175
|
-
private static handleTranspile(
|
|
230
|
+
private static async handleTranspile(
|
|
176
231
|
params?: Record<string, unknown>,
|
|
177
|
-
): IMethodResult {
|
|
232
|
+
): Promise<IMethodResult> {
|
|
178
233
|
if (!params || typeof params.source !== "string") {
|
|
179
234
|
return {
|
|
180
235
|
success: false,
|
|
@@ -183,7 +238,26 @@ class ServeCommand {
|
|
|
183
238
|
};
|
|
184
239
|
}
|
|
185
240
|
|
|
186
|
-
|
|
241
|
+
if (!ServeCommand.transpiler) {
|
|
242
|
+
return {
|
|
243
|
+
success: false,
|
|
244
|
+
errorCode: JsonRpcHandler.ERROR_INVALID_PARAMS,
|
|
245
|
+
errorMessage: "Server not initialized. Call initialize first.",
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const source = String(params.source);
|
|
250
|
+
const filePath =
|
|
251
|
+
typeof params.filePath === "string" ? params.filePath : undefined;
|
|
252
|
+
|
|
253
|
+
const options = filePath
|
|
254
|
+
? { workingDir: dirname(filePath), sourcePath: filePath }
|
|
255
|
+
: undefined;
|
|
256
|
+
|
|
257
|
+
const result = await ServeCommand.transpiler.transpileSource(
|
|
258
|
+
source,
|
|
259
|
+
options,
|
|
260
|
+
);
|
|
187
261
|
|
|
188
262
|
return {
|
|
189
263
|
success: true,
|
|
@@ -191,16 +265,19 @@ class ServeCommand {
|
|
|
191
265
|
success: result.success,
|
|
192
266
|
code: result.code,
|
|
193
267
|
errors: result.errors,
|
|
268
|
+
cppDetected: ServeCommand.transpiler.isCppDetected(),
|
|
194
269
|
},
|
|
195
270
|
};
|
|
196
271
|
}
|
|
197
272
|
|
|
198
273
|
/**
|
|
199
274
|
* Handle parseSymbols method
|
|
275
|
+
* Runs full transpilation for include/C++ detection, then extracts symbols
|
|
276
|
+
* from the parse tree (preserving "extract symbols even with parse errors" behavior)
|
|
200
277
|
*/
|
|
201
|
-
private static handleParseSymbols(
|
|
278
|
+
private static async handleParseSymbols(
|
|
202
279
|
params?: Record<string, unknown>,
|
|
203
|
-
): IMethodResult {
|
|
280
|
+
): Promise<IMethodResult> {
|
|
204
281
|
if (!params || typeof params.source !== "string") {
|
|
205
282
|
return {
|
|
206
283
|
success: false,
|
|
@@ -209,22 +286,64 @@ class ServeCommand {
|
|
|
209
286
|
};
|
|
210
287
|
}
|
|
211
288
|
|
|
212
|
-
const
|
|
289
|
+
const source = String(params.source);
|
|
290
|
+
const filePath =
|
|
291
|
+
typeof params.filePath === "string" ? params.filePath : undefined;
|
|
292
|
+
|
|
293
|
+
// If transpiler is initialized, run transpileSource to trigger header
|
|
294
|
+
// resolution and C++ detection (results are discarded, we just want
|
|
295
|
+
// the side effects on the symbol table)
|
|
296
|
+
if (ServeCommand.transpiler && filePath) {
|
|
297
|
+
try {
|
|
298
|
+
await ServeCommand.transpiler.transpileSource(source, {
|
|
299
|
+
workingDir: dirname(filePath),
|
|
300
|
+
sourcePath: filePath,
|
|
301
|
+
});
|
|
302
|
+
} catch {
|
|
303
|
+
// Ignore transpilation errors - we still extract symbols below
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Delegate symbol extraction to parseWithSymbols (shared with WorkspaceIndex)
|
|
308
|
+
const result = parseWithSymbols(source);
|
|
213
309
|
|
|
214
310
|
return {
|
|
215
311
|
success: true,
|
|
216
|
-
result
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
312
|
+
result,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Handle parseCHeader method
|
|
318
|
+
* Parses C/C++ header files and extracts symbols
|
|
319
|
+
*/
|
|
320
|
+
private static async handleParseCHeader(
|
|
321
|
+
params?: Record<string, unknown>,
|
|
322
|
+
): Promise<IMethodResult> {
|
|
323
|
+
if (!params || typeof params.source !== "string") {
|
|
324
|
+
return {
|
|
325
|
+
success: false,
|
|
326
|
+
errorCode: JsonRpcHandler.ERROR_INVALID_PARAMS,
|
|
327
|
+
errorMessage: "Missing required param: source",
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const source = String(params.source);
|
|
332
|
+
const filePath =
|
|
333
|
+
typeof params.filePath === "string" ? params.filePath : undefined;
|
|
334
|
+
|
|
335
|
+
const result = parseCHeader(source, filePath);
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
success: true,
|
|
339
|
+
result,
|
|
221
340
|
};
|
|
222
341
|
}
|
|
223
342
|
|
|
224
343
|
/**
|
|
225
344
|
* Handle shutdown method
|
|
226
345
|
*/
|
|
227
|
-
private static handleShutdown(): IMethodResult {
|
|
346
|
+
private static async handleShutdown(): Promise<IMethodResult> {
|
|
228
347
|
ServeCommand.shouldShutdown = true;
|
|
229
348
|
return {
|
|
230
349
|
success: true,
|
|
@@ -25,7 +25,7 @@ describe("ServeCommand", () => {
|
|
|
25
25
|
stdoutWriteSpy.mockRestore();
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
// Helper to send a request and capture the response
|
|
28
|
+
// Helper to send a request and capture the async response
|
|
29
29
|
async function sendRequest(request: object): Promise<object> {
|
|
30
30
|
const line = JSON.stringify(request);
|
|
31
31
|
// Access private handleLine method for testing
|
|
@@ -34,8 +34,14 @@ describe("ServeCommand", () => {
|
|
|
34
34
|
).handleLine.bind(ServeCommand);
|
|
35
35
|
handleLine(line);
|
|
36
36
|
|
|
37
|
-
//
|
|
38
|
-
|
|
37
|
+
// Wait for async dispatch to complete and write response
|
|
38
|
+
await vi.waitFor(() => {
|
|
39
|
+
expect(stdoutWriteSpy.mock.calls.length).toBeGreaterThan(0);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Parse the response from the latest stdout.write call
|
|
43
|
+
const writeCall =
|
|
44
|
+
stdoutWriteSpy.mock.calls[stdoutWriteSpy.mock.calls.length - 1];
|
|
39
45
|
if (writeCall) {
|
|
40
46
|
const responseStr = writeCall[0] as string;
|
|
41
47
|
return JSON.parse(responseStr.trim());
|
|
@@ -54,8 +60,67 @@ describe("ServeCommand", () => {
|
|
|
54
60
|
});
|
|
55
61
|
});
|
|
56
62
|
|
|
63
|
+
describe("initialize", () => {
|
|
64
|
+
it("initializes with workspace path", async () => {
|
|
65
|
+
const response = await sendRequest({
|
|
66
|
+
id: 10,
|
|
67
|
+
method: "initialize",
|
|
68
|
+
params: { workspacePath: "/tmp" },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(response).toMatchObject({
|
|
72
|
+
id: 10,
|
|
73
|
+
result: { success: true },
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("returns error for missing workspacePath param", async () => {
|
|
78
|
+
const response = await sendRequest({
|
|
79
|
+
id: 11,
|
|
80
|
+
method: "initialize",
|
|
81
|
+
params: {},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(response).toMatchObject({
|
|
85
|
+
id: 11,
|
|
86
|
+
error: {
|
|
87
|
+
code: JsonRpcHandler.ERROR_INVALID_PARAMS,
|
|
88
|
+
message: "Missing required param: workspacePath",
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
57
94
|
describe("transpile", () => {
|
|
95
|
+
it("returns error when server not initialized", async () => {
|
|
96
|
+
// Reset transpiler to null by accessing private field
|
|
97
|
+
(ServeCommand as unknown as { transpiler: null }).transpiler = null;
|
|
98
|
+
stdoutWriteSpy.mockClear();
|
|
99
|
+
|
|
100
|
+
const response = await sendRequest({
|
|
101
|
+
id: 50,
|
|
102
|
+
method: "transpile",
|
|
103
|
+
params: { source: "u8 x <- 5;" },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(response).toMatchObject({
|
|
107
|
+
id: 50,
|
|
108
|
+
error: {
|
|
109
|
+
code: JsonRpcHandler.ERROR_INVALID_PARAMS,
|
|
110
|
+
message: "Server not initialized. Call initialize first.",
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
58
115
|
it("transpiles valid C-Next source", async () => {
|
|
116
|
+
// Initialize first (required for transpile)
|
|
117
|
+
await sendRequest({
|
|
118
|
+
id: 100,
|
|
119
|
+
method: "initialize",
|
|
120
|
+
params: { workspacePath: "/tmp" },
|
|
121
|
+
});
|
|
122
|
+
stdoutWriteSpy.mockClear();
|
|
123
|
+
|
|
59
124
|
const response = await sendRequest({
|
|
60
125
|
id: 2,
|
|
61
126
|
method: "transpile",
|
|
@@ -73,6 +138,14 @@ describe("ServeCommand", () => {
|
|
|
73
138
|
});
|
|
74
139
|
|
|
75
140
|
it("returns errors for invalid source", async () => {
|
|
141
|
+
// Initialize first
|
|
142
|
+
await sendRequest({
|
|
143
|
+
id: 101,
|
|
144
|
+
method: "initialize",
|
|
145
|
+
params: { workspacePath: "/tmp" },
|
|
146
|
+
});
|
|
147
|
+
stdoutWriteSpy.mockClear();
|
|
148
|
+
|
|
76
149
|
const response = await sendRequest({
|
|
77
150
|
id: 3,
|
|
78
151
|
method: "transpile",
|
|
@@ -139,6 +212,35 @@ describe("ServeCommand", () => {
|
|
|
139
212
|
);
|
|
140
213
|
});
|
|
141
214
|
|
|
215
|
+
it("parses symbols with filePath after initialization", async () => {
|
|
216
|
+
// Initialize first
|
|
217
|
+
await sendRequest({
|
|
218
|
+
id: 60,
|
|
219
|
+
method: "initialize",
|
|
220
|
+
params: { workspacePath: "/tmp" },
|
|
221
|
+
});
|
|
222
|
+
stdoutWriteSpy.mockClear();
|
|
223
|
+
|
|
224
|
+
const response = await sendRequest({
|
|
225
|
+
id: 61,
|
|
226
|
+
method: "parseSymbols",
|
|
227
|
+
params: {
|
|
228
|
+
source: "void myFunc() { }",
|
|
229
|
+
filePath: "/tmp/test.cnx",
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const result = response as {
|
|
234
|
+
id: number;
|
|
235
|
+
result: { success: boolean; symbols: Array<{ name: string }> };
|
|
236
|
+
};
|
|
237
|
+
expect(result.id).toBe(61);
|
|
238
|
+
expect(result.result.success).toBe(true);
|
|
239
|
+
expect(result.result.symbols).toEqual(
|
|
240
|
+
expect.arrayContaining([expect.objectContaining({ name: "myFunc" })]),
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
142
244
|
it("returns error for missing source param", async () => {
|
|
143
245
|
const response = await sendRequest({
|
|
144
246
|
id: 7,
|
|
@@ -154,6 +256,95 @@ describe("ServeCommand", () => {
|
|
|
154
256
|
},
|
|
155
257
|
});
|
|
156
258
|
});
|
|
259
|
+
|
|
260
|
+
it("returns error for missing params", async () => {
|
|
261
|
+
const response = await sendRequest({
|
|
262
|
+
id: 70,
|
|
263
|
+
method: "parseSymbols",
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(response).toMatchObject({
|
|
267
|
+
id: 70,
|
|
268
|
+
error: {
|
|
269
|
+
code: JsonRpcHandler.ERROR_INVALID_PARAMS,
|
|
270
|
+
message: "Missing required param: source",
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe("parseCHeader", () => {
|
|
277
|
+
it("parses symbols from C header source", async () => {
|
|
278
|
+
const response = await sendRequest({
|
|
279
|
+
id: 71,
|
|
280
|
+
method: "parseCHeader",
|
|
281
|
+
params: { source: "int myFunction(void);" },
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const result = response as {
|
|
285
|
+
id: number;
|
|
286
|
+
result: { success: boolean; symbols: Array<{ name: string }> };
|
|
287
|
+
};
|
|
288
|
+
expect(result.id).toBe(71);
|
|
289
|
+
expect(result.result.success).toBe(true);
|
|
290
|
+
expect(result.result.symbols).toEqual(
|
|
291
|
+
expect.arrayContaining([
|
|
292
|
+
expect.objectContaining({ name: "myFunction" }),
|
|
293
|
+
]),
|
|
294
|
+
);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("parses struct definitions", async () => {
|
|
298
|
+
const response = await sendRequest({
|
|
299
|
+
id: 72,
|
|
300
|
+
method: "parseCHeader",
|
|
301
|
+
params: {
|
|
302
|
+
source: "typedef struct { int x; int y; } Point;",
|
|
303
|
+
filePath: "/tmp/types.h",
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const result = response as {
|
|
308
|
+
id: number;
|
|
309
|
+
result: { success: boolean; symbols: Array<{ name: string }> };
|
|
310
|
+
};
|
|
311
|
+
expect(result.id).toBe(72);
|
|
312
|
+
expect(result.result.success).toBe(true);
|
|
313
|
+
expect(result.result.symbols).toEqual(
|
|
314
|
+
expect.arrayContaining([expect.objectContaining({ name: "Point" })]),
|
|
315
|
+
);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("returns error for missing source param", async () => {
|
|
319
|
+
const response = await sendRequest({
|
|
320
|
+
id: 73,
|
|
321
|
+
method: "parseCHeader",
|
|
322
|
+
params: {},
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
expect(response).toMatchObject({
|
|
326
|
+
id: 73,
|
|
327
|
+
error: {
|
|
328
|
+
code: JsonRpcHandler.ERROR_INVALID_PARAMS,
|
|
329
|
+
message: "Missing required param: source",
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("returns error for missing params", async () => {
|
|
335
|
+
const response = await sendRequest({
|
|
336
|
+
id: 74,
|
|
337
|
+
method: "parseCHeader",
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
expect(response).toMatchObject({
|
|
341
|
+
id: 74,
|
|
342
|
+
error: {
|
|
343
|
+
code: JsonRpcHandler.ERROR_INVALID_PARAMS,
|
|
344
|
+
message: "Missing required param: source",
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
});
|
|
157
348
|
});
|
|
158
349
|
|
|
159
350
|
describe("shutdown", () => {
|
|
@@ -197,11 +388,44 @@ describe("ServeCommand", () => {
|
|
|
197
388
|
handleLine("");
|
|
198
389
|
handleLine(" ");
|
|
199
390
|
|
|
391
|
+
// Wait a tick to ensure any async work would have started
|
|
392
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
393
|
+
|
|
200
394
|
// No response should be written for empty lines
|
|
201
395
|
expect(stdoutWriteSpy).not.toHaveBeenCalled();
|
|
202
396
|
});
|
|
203
397
|
});
|
|
204
398
|
|
|
399
|
+
describe("debug logging", () => {
|
|
400
|
+
it("writes debug messages to stderr when debug mode is enabled", async () => {
|
|
401
|
+
const stderrWriteSpy = vi
|
|
402
|
+
.spyOn(process.stderr, "write")
|
|
403
|
+
.mockImplementation(() => true);
|
|
404
|
+
|
|
405
|
+
// Enable debug mode via private field
|
|
406
|
+
(ServeCommand as unknown as { debugMode: boolean }).debugMode = true;
|
|
407
|
+
|
|
408
|
+
const response = await sendRequest({
|
|
409
|
+
id: 80,
|
|
410
|
+
method: "getVersion",
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
expect(response).toMatchObject({
|
|
414
|
+
id: 80,
|
|
415
|
+
result: { version: expect.any(String) },
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// Debug logs should have been written to stderr
|
|
419
|
+
expect(stderrWriteSpy).toHaveBeenCalled();
|
|
420
|
+
const debugOutput = stderrWriteSpy.mock.calls.map((c) => c[0]).join("");
|
|
421
|
+
expect(debugOutput).toContain("[serve]");
|
|
422
|
+
|
|
423
|
+
// Clean up
|
|
424
|
+
(ServeCommand as unknown as { debugMode: boolean }).debugMode = false;
|
|
425
|
+
stderrWriteSpy.mockRestore();
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
|
|
205
429
|
describe("invalid JSON", () => {
|
|
206
430
|
it("returns parse error", async () => {
|
|
207
431
|
const handleLine = (
|