@mrclrchtr/supi-tree-sitter 8.0.1 → 9.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 +12 -3
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +10 -1
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +9 -4
- package/node_modules/@mrclrchtr/supi-core/src/session-capabilities.ts +55 -1
- package/node_modules/@mrclrchtr/supi-core/src/session-capability-profiles.ts +240 -0
- package/node_modules/@mrclrchtr/supi-core/src/session.ts +22 -0
- package/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
- package/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +7 -7
- package/node_modules/web-tree-sitter/package.json +1 -1
- package/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
- package/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +7 -7
- package/package.json +6 -4
- package/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +1 -1
- package/resources/grammars/latex/LICENSE +21 -0
- package/resources/grammars/latex/tree-sitter-latex.wasm +0 -0
- package/resources/grammars/latex/tree-sitter-latex.wasm.json +17 -0
- package/resources/grammars/sql/tree-sitter-sql.wasm.json +1 -1
- package/src/language.ts +5 -1
- package/src/operation-support.ts +6 -0
- package/src/provider/tree-sitter-provider.ts +2 -0
- package/src/session/structural-worker-client.ts +45 -34
- package/src/session/structural-worker-process.ts +122 -0
- package/src/session/structural-worker-protocol.ts +1 -1
- package/src/tool/callees.ts +1 -0
- package/src/tool/imports.ts +55 -1
- package/src/tool/outline-latex.ts +288 -0
- package/src/tool/outline-polyglot.ts +8 -1
- package/src/tool/outline.ts +18 -4
- package/src/types.ts +11 -3
- package/src/worker/process-host.mjs +92 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-tree-sitter",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.1.0",
|
|
4
4
|
"description": "Structural AST analysis for SuPi code intelligence",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"jiti": "^2.7.0",
|
|
38
38
|
"web-tree-sitter": "^0.27.0",
|
|
39
|
-
"@mrclrchtr/supi-code-runtime": "
|
|
40
|
-
"@mrclrchtr/supi-core": "
|
|
39
|
+
"@mrclrchtr/supi-code-runtime": "9.1.0",
|
|
40
|
+
"@mrclrchtr/supi-core": "9.1.0"
|
|
41
41
|
},
|
|
42
42
|
"bundledDependencies": [
|
|
43
43
|
"@mrclrchtr/supi-code-runtime",
|
|
@@ -59,6 +59,8 @@
|
|
|
59
59
|
"generate:kotlin-wasm": "node scripts/generate-kotlin-wasm.mjs",
|
|
60
60
|
"check:kotlin-wasm": "node scripts/generate-kotlin-wasm.mjs --check",
|
|
61
61
|
"generate:sql-wasm": "node scripts/generate-sql-wasm.mjs",
|
|
62
|
-
"check:sql-wasm": "node scripts/generate-sql-wasm.mjs --check"
|
|
62
|
+
"check:sql-wasm": "node scripts/generate-sql-wasm.mjs --check",
|
|
63
|
+
"generate:latex-wasm": "node scripts/generate-latex-wasm.mjs",
|
|
64
|
+
"check:latex-wasm": "node scripts/generate-latex-wasm.mjs --check"
|
|
63
65
|
}
|
|
64
66
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"releaseAsset": "https://github.com/fwcd/tree-sitter-kotlin/releases/download/0.3.8/tree-sitter-kotlin.wasm"
|
|
7
7
|
},
|
|
8
8
|
"generatedWith": {
|
|
9
|
-
"treeSitterCli": "0.27.
|
|
9
|
+
"treeSitterCli": "0.27.1"
|
|
10
10
|
},
|
|
11
11
|
"sha256": "7f522f2e3ef633735b3e2bdea165071b5ad0a99d6201e84acab49017590f812a"
|
|
12
12
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Patrick Förster
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": {
|
|
3
|
+
"npmPackage": "@pfoerster/tree-sitter-latex",
|
|
4
|
+
"version": "0.6.0",
|
|
5
|
+
"repository": "https://github.com/latex-lsp/tree-sitter-latex"
|
|
6
|
+
},
|
|
7
|
+
"generatedWith": {
|
|
8
|
+
"treeSitterCli": "0.27.1"
|
|
9
|
+
},
|
|
10
|
+
"patches": [
|
|
11
|
+
{
|
|
12
|
+
"id": "label-underscore-v1",
|
|
13
|
+
"file": "grammar.js"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"sha256": "122e3587a1faaed6316c2fadb2336e531252787ea541f897a966b0dc0c812d52"
|
|
17
|
+
}
|
package/src/language.ts
CHANGED
|
@@ -48,6 +48,9 @@ const EXTENSION_GRAMMAR = {
|
|
|
48
48
|
".xhtml": "html",
|
|
49
49
|
".r": "r",
|
|
50
50
|
".sql": "sql",
|
|
51
|
+
".tex": "latex",
|
|
52
|
+
".sty": "latex",
|
|
53
|
+
".cls": "latex",
|
|
51
54
|
} satisfies Record<SupportedExtension, GrammarId>;
|
|
52
55
|
|
|
53
56
|
const SUPPORTED_EXTENSIONS = Object.keys(EXTENSION_GRAMMAR) as SupportedExtension[];
|
|
@@ -87,6 +90,7 @@ const GRAMMAR_WASM: Record<GrammarId, string> = {
|
|
|
87
90
|
html: "tree-sitter-html.wasm",
|
|
88
91
|
r: "tree-sitter-r.wasm",
|
|
89
92
|
sql: "tree-sitter-sql.wasm",
|
|
93
|
+
latex: "tree-sitter-latex.wasm",
|
|
90
94
|
};
|
|
91
95
|
|
|
92
96
|
const resourcesDir = path.resolve(sourceDir, "../resources/grammars");
|
|
@@ -103,7 +107,7 @@ export function resolveGrammarWasmPath(grammarId: GrammarId): string {
|
|
|
103
107
|
if (!fs.existsSync(vendoredPath)) {
|
|
104
108
|
throw new Error(
|
|
105
109
|
`Vendored WASM grammar not found for "${grammarId}": expected at ${vendoredPath}. ` +
|
|
106
|
-
"Run `
|
|
110
|
+
"Run `pnpm --filter @mrclrchtr/supi-tree-sitter generate:all-wasm` or reinstall the package.",
|
|
107
111
|
);
|
|
108
112
|
}
|
|
109
113
|
|
package/src/operation-support.ts
CHANGED
|
@@ -33,6 +33,12 @@ export function supportsGrammarOperation(
|
|
|
33
33
|
case "outline":
|
|
34
34
|
return true;
|
|
35
35
|
case "imports":
|
|
36
|
+
return (
|
|
37
|
+
grammar === "javascript" ||
|
|
38
|
+
grammar === "typescript" ||
|
|
39
|
+
grammar === "tsx" ||
|
|
40
|
+
grammar === "latex"
|
|
41
|
+
);
|
|
36
42
|
case "exports":
|
|
37
43
|
return grammar === "javascript" || grammar === "typescript" || grammar === "tsx";
|
|
38
44
|
default:
|
|
@@ -103,6 +103,8 @@ function mapOutlineItems(items: OutlineItem[]): OutlineData[] {
|
|
|
103
103
|
return items.map((item) => ({
|
|
104
104
|
name: item.name,
|
|
105
105
|
kind: item.kind,
|
|
106
|
+
...(item.nameAnchor ? { nameAnchor: { ...item.nameAnchor } } : {}),
|
|
107
|
+
...(item.nameEndAnchor ? { nameEndAnchor: { ...item.nameEndAnchor } } : {}),
|
|
106
108
|
...takeRange(item.range),
|
|
107
109
|
children: item.children ? mapOutlineItems(item.children) : undefined,
|
|
108
110
|
}));
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// biome-ignore-all lint/style/noExcessiveLinesPerFile: one mailbox state machine owns all Worker lifecycle transitions
|
|
2
2
|
import { setImmediate as yieldImmediate } from "node:timers/promises";
|
|
3
|
-
import { Worker } from "node:worker_threads";
|
|
4
3
|
import {
|
|
5
4
|
type CodeRequestControl,
|
|
6
5
|
CodeRequestDeadlineError,
|
|
@@ -11,6 +10,10 @@ import type { TreeSitterResult } from "../types.ts";
|
|
|
11
10
|
import { publishStructuralTimingEvent } from "./structural-timing.ts";
|
|
12
11
|
import { StructuralWorkerLifecycle } from "./structural-worker-client-lifecycle.ts";
|
|
13
12
|
import { assertStructuralProtocolMessageSize } from "./structural-worker-message-size.ts";
|
|
13
|
+
import {
|
|
14
|
+
createStructuralWorkerProcess,
|
|
15
|
+
type StructuralWorkerLike,
|
|
16
|
+
} from "./structural-worker-process.ts";
|
|
14
17
|
import {
|
|
15
18
|
decodeStructuralResult,
|
|
16
19
|
type ParentToStructuralWorkerMessage,
|
|
@@ -22,14 +25,6 @@ import {
|
|
|
22
25
|
validateWorkerToParentMessage,
|
|
23
26
|
} from "./structural-worker-protocol.ts";
|
|
24
27
|
|
|
25
|
-
interface StructuralWorkerLike {
|
|
26
|
-
postMessage(message: ParentToStructuralWorkerMessage): void;
|
|
27
|
-
on(event: "message", listener: (message: unknown) => void): this;
|
|
28
|
-
on(event: "error", listener: (error: Error) => void): this;
|
|
29
|
-
on(event: "exit", listener: (code: number) => void): this;
|
|
30
|
-
terminate(): Promise<number>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
28
|
export type StructuralWorkerFactory = (options: {
|
|
34
29
|
readonly cwd: string;
|
|
35
30
|
readonly generation: number;
|
|
@@ -71,6 +66,7 @@ interface ActiveWorker {
|
|
|
71
66
|
readonly worker: StructuralWorkerLike;
|
|
72
67
|
ready: boolean;
|
|
73
68
|
startupSettled: boolean;
|
|
69
|
+
failureStarted: boolean;
|
|
74
70
|
readonly settleStartupFailure: (message: string) => void;
|
|
75
71
|
}
|
|
76
72
|
|
|
@@ -198,6 +194,7 @@ export class StructuralWorkerClient {
|
|
|
198
194
|
worker,
|
|
199
195
|
ready: false,
|
|
200
196
|
startupSettled: false,
|
|
197
|
+
failureStarted: false,
|
|
201
198
|
settleStartupFailure: (message) => rejectStartup(message),
|
|
202
199
|
};
|
|
203
200
|
this.#worker = active;
|
|
@@ -241,23 +238,18 @@ export class StructuralWorkerClient {
|
|
|
241
238
|
});
|
|
242
239
|
worker.on("error", (error) => {
|
|
243
240
|
settleFailure(error.message);
|
|
244
|
-
if (this.#
|
|
245
|
-
void this.#lifecycle.run(() => this.#workerFailure(error.message));
|
|
246
|
-
}
|
|
241
|
+
if (active.ready) this.#failWorker(active, error.message);
|
|
247
242
|
});
|
|
248
243
|
worker.on("exit", (code) => {
|
|
249
244
|
settleFailure(`Structural Worker exited with code ${code}`);
|
|
250
|
-
if (
|
|
251
|
-
void this.#lifecycle.run(() =>
|
|
252
|
-
this.#workerFailure(`Structural Worker exited with code ${code}`),
|
|
253
|
-
);
|
|
254
|
-
}
|
|
245
|
+
if (active.ready) this.#failWorker(active, `Structural Worker exited with code ${code}`);
|
|
255
246
|
});
|
|
256
247
|
});
|
|
257
248
|
}
|
|
258
249
|
|
|
259
250
|
#dispatch(): void {
|
|
260
|
-
|
|
251
|
+
const worker = this.#worker;
|
|
252
|
+
if (this.#closed || this.#active || !worker?.ready) return;
|
|
261
253
|
while (this.#queue.length > 0) {
|
|
262
254
|
const request = this.#queue.shift();
|
|
263
255
|
if (!request) return;
|
|
@@ -271,7 +263,7 @@ export class StructuralWorkerClient {
|
|
|
271
263
|
const message = {
|
|
272
264
|
kind: "request",
|
|
273
265
|
version: STRUCTURAL_WORKER_PROTOCOL_VERSION,
|
|
274
|
-
generation:
|
|
266
|
+
generation: worker.generation,
|
|
275
267
|
requestId: request.id,
|
|
276
268
|
input: request.input,
|
|
277
269
|
...(request.control?.operationId ? { operationId: request.control.operationId } : {}),
|
|
@@ -280,7 +272,7 @@ export class StructuralWorkerClient {
|
|
|
280
272
|
} as const;
|
|
281
273
|
try {
|
|
282
274
|
assertStructuralProtocolMessageSize(message);
|
|
283
|
-
this.#worker
|
|
275
|
+
if (!this.#send(worker, message)) return;
|
|
284
276
|
} catch (error) {
|
|
285
277
|
this.#active = null;
|
|
286
278
|
this.#settle(request, runtimeError(errorMessage(error)));
|
|
@@ -334,12 +326,13 @@ export class StructuralWorkerClient {
|
|
|
334
326
|
active.awaitingAck = message.sequence;
|
|
335
327
|
if (message.final) active.finalSequence = message.sequence;
|
|
336
328
|
await yieldImmediate();
|
|
337
|
-
|
|
329
|
+
const worker = this.#worker;
|
|
330
|
+
if (this.#active !== active || !worker) return;
|
|
338
331
|
active.awaitingAck = null;
|
|
339
|
-
this.#worker
|
|
332
|
+
this.#send(worker, {
|
|
340
333
|
kind: "chunk-ack",
|
|
341
334
|
version: STRUCTURAL_WORKER_PROTOCOL_VERSION,
|
|
342
|
-
generation:
|
|
335
|
+
generation: worker.generation,
|
|
343
336
|
requestId: active.id,
|
|
344
337
|
sequence: message.sequence,
|
|
345
338
|
});
|
|
@@ -401,6 +394,22 @@ export class StructuralWorkerClient {
|
|
|
401
394
|
};
|
|
402
395
|
}
|
|
403
396
|
|
|
397
|
+
#send(worker: ActiveWorker, message: ParentToStructuralWorkerMessage): boolean {
|
|
398
|
+
try {
|
|
399
|
+
worker.worker.postMessage(message);
|
|
400
|
+
return true;
|
|
401
|
+
} catch (error) {
|
|
402
|
+
this.#failWorker(worker, errorMessage(error));
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
#failWorker(worker: ActiveWorker, reason: string): void {
|
|
408
|
+
if (this.#closed || this.#worker !== worker || worker.failureStarted) return;
|
|
409
|
+
worker.failureStarted = true;
|
|
410
|
+
void this.#lifecycle.run(() => this.#workerFailure(reason)).catch(() => undefined);
|
|
411
|
+
}
|
|
412
|
+
|
|
404
413
|
#interrupt(request: PendingRequest<unknown>, error: unknown): void {
|
|
405
414
|
if (request.settled) return;
|
|
406
415
|
const queuedIndex = this.#queue.indexOf(request);
|
|
@@ -409,14 +418,19 @@ export class StructuralWorkerClient {
|
|
|
409
418
|
this.#reject(request, error);
|
|
410
419
|
return;
|
|
411
420
|
}
|
|
412
|
-
|
|
421
|
+
const worker = this.#worker;
|
|
422
|
+
if (this.#active !== request || !worker) return;
|
|
413
423
|
Atomics.store(request.cancellationFlag, 0, 1);
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
424
|
+
if (
|
|
425
|
+
!this.#send(worker, {
|
|
426
|
+
kind: "cancel",
|
|
427
|
+
version: STRUCTURAL_WORKER_PROTOCOL_VERSION,
|
|
428
|
+
generation: worker.generation,
|
|
429
|
+
requestId: request.id,
|
|
430
|
+
})
|
|
431
|
+
) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
420
434
|
request.hardStopTimer = setTimeout(
|
|
421
435
|
() => void this.#hardStop(request, error),
|
|
422
436
|
STRUCTURAL_WORKER_LIMITS.hardStopGraceMs,
|
|
@@ -550,10 +564,7 @@ function createProductionWorker(options: {
|
|
|
550
564
|
cwd: string;
|
|
551
565
|
generation: number;
|
|
552
566
|
}): StructuralWorkerLike {
|
|
553
|
-
return
|
|
554
|
-
execArgv: [],
|
|
555
|
-
workerData: options,
|
|
556
|
-
});
|
|
567
|
+
return createStructuralWorkerProcess(options);
|
|
557
568
|
}
|
|
558
569
|
|
|
559
570
|
function runtimeError<T = never>(message: string): TreeSitterResult<T> {
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { type ChildProcess, fork } from "node:child_process";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import type {
|
|
5
|
+
ParentToStructuralWorkerMessage,
|
|
6
|
+
StructuralWorkerToParentMessage,
|
|
7
|
+
} from "./structural-worker-protocol.ts";
|
|
8
|
+
|
|
9
|
+
const PARENT_CHANNEL = "supi-tree-sitter-parent";
|
|
10
|
+
const WORKER_CHANNEL = "supi-tree-sitter-worker";
|
|
11
|
+
|
|
12
|
+
export interface StructuralWorkerLike {
|
|
13
|
+
postMessage(message: ParentToStructuralWorkerMessage): void;
|
|
14
|
+
on(event: "message", listener: (message: unknown) => void): this;
|
|
15
|
+
on(event: "error", listener: (error: Error) => void): this;
|
|
16
|
+
on(event: "exit", listener: (code: number) => void): this;
|
|
17
|
+
terminate(): Promise<number>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Start the Structural Worker in a Node process with safe WebAssembly compilation. */
|
|
21
|
+
export function createStructuralWorkerProcess(options: {
|
|
22
|
+
readonly cwd: string;
|
|
23
|
+
readonly generation: number;
|
|
24
|
+
}): StructuralWorkerLike {
|
|
25
|
+
const hostPath = fileURLToPath(new URL("../worker/process-host.mjs", import.meta.url));
|
|
26
|
+
// A child process accepts the V8 flag that a Worker thread rejects.
|
|
27
|
+
const child = fork(hostPath, [options.cwd, String(options.generation)], {
|
|
28
|
+
execArgv: ["--liftoff-only"],
|
|
29
|
+
env: { ...process.env, NODE_OPTIONS: "" },
|
|
30
|
+
serialization: "advanced",
|
|
31
|
+
stdio: ["ignore", "ignore", "inherit", "ipc"],
|
|
32
|
+
windowsHide: true,
|
|
33
|
+
});
|
|
34
|
+
return new StructuralWorkerProcess(child);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class StructuralWorkerProcess implements StructuralWorkerLike {
|
|
38
|
+
readonly #events = new EventEmitter();
|
|
39
|
+
readonly #child: ChildProcess;
|
|
40
|
+
|
|
41
|
+
constructor(child: ChildProcess) {
|
|
42
|
+
this.#child = child;
|
|
43
|
+
child.on("message", (value: unknown) => this.#receive(value));
|
|
44
|
+
child.on("error", (error) => this.#events.emit("error", error));
|
|
45
|
+
child.on("exit", (code) => this.#events.emit("exit", code ?? 1));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
postMessage(message: ParentToStructuralWorkerMessage): void {
|
|
49
|
+
if (!this.#child.connected) throw new Error("Structural Worker process is disconnected");
|
|
50
|
+
// The process host creates a shared cancellation flag for its Worker thread.
|
|
51
|
+
const forwarded =
|
|
52
|
+
message.kind === "request" ? { ...message, cancellationFlag: undefined } : message;
|
|
53
|
+
this.#child.send({ channel: PARENT_CHANNEL, message: forwarded }, (error) => {
|
|
54
|
+
if (error) this.#events.emit("error", error);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
on(event: "message", listener: (message: unknown) => void): this;
|
|
59
|
+
on(event: "error", listener: (error: Error) => void): this;
|
|
60
|
+
on(event: "exit", listener: (code: number) => void): this;
|
|
61
|
+
on(
|
|
62
|
+
event: "message" | "error" | "exit",
|
|
63
|
+
listener: ((message: unknown) => void) | ((error: Error) => void) | ((code: number) => void),
|
|
64
|
+
): this {
|
|
65
|
+
this.#events.on(event, listener);
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
terminate(): Promise<number> {
|
|
70
|
+
if (this.#child.exitCode !== null) return Promise.resolve(this.#child.exitCode ?? 0);
|
|
71
|
+
if (this.#child.signalCode !== null) return Promise.resolve(0);
|
|
72
|
+
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
let settled = false;
|
|
75
|
+
const finish = (code: number | null) => {
|
|
76
|
+
if (settled) return;
|
|
77
|
+
settled = true;
|
|
78
|
+
clearTimeout(forceStopTimer);
|
|
79
|
+
resolve(code ?? 0);
|
|
80
|
+
};
|
|
81
|
+
const forceStopTimer = setTimeout(() => {
|
|
82
|
+
if (!this.#child.kill("SIGKILL")) finish(this.#child.exitCode);
|
|
83
|
+
}, 250);
|
|
84
|
+
forceStopTimer.unref?.();
|
|
85
|
+
this.#child.once("exit", (code) => finish(code));
|
|
86
|
+
if (!this.#child.kill("SIGTERM")) finish(this.#child.exitCode);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#receive(value: unknown): void {
|
|
91
|
+
if (!isWorkerChannel(value)) {
|
|
92
|
+
this.#events.emit("message", value);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (value.message.kind === "worker-error") {
|
|
96
|
+
this.#events.emit("error", new Error(value.message.message));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.#events.emit("message", value.message);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface WorkerChannelMessage {
|
|
104
|
+
readonly channel: typeof WORKER_CHANNEL;
|
|
105
|
+
readonly message:
|
|
106
|
+
| StructuralWorkerToParentMessage
|
|
107
|
+
| { readonly kind: "worker-error"; readonly message: string };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isWorkerChannel(value: unknown): value is WorkerChannelMessage {
|
|
111
|
+
if (!value || typeof value !== "object") return false;
|
|
112
|
+
const candidate = value as Partial<WorkerChannelMessage>;
|
|
113
|
+
if (
|
|
114
|
+
candidate.channel !== WORKER_CHANNEL ||
|
|
115
|
+
!candidate.message ||
|
|
116
|
+
typeof candidate.message !== "object"
|
|
117
|
+
) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const message = candidate.message as { kind?: unknown; message?: unknown };
|
|
121
|
+
return message.kind !== "worker-error" || typeof message.message === "string";
|
|
122
|
+
}
|
|
@@ -515,7 +515,7 @@ function isFiniteDuration(value: unknown): boolean {
|
|
|
515
515
|
}
|
|
516
516
|
|
|
517
517
|
function hasExactKeys(value: Record<string, unknown>, expected: readonly string[]): boolean {
|
|
518
|
-
const actual = Object.keys(value)
|
|
518
|
+
const actual = Object.keys(value);
|
|
519
519
|
return actual.length === expected.length && expected.every((key) => actual.includes(key));
|
|
520
520
|
}
|
|
521
521
|
|
package/src/tool/callees.ts
CHANGED
package/src/tool/imports.ts
CHANGED
|
@@ -19,7 +19,11 @@ export async function extractImports(
|
|
|
19
19
|
const imports: ImportRecord[] = [];
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
|
-
|
|
22
|
+
if (parseResult.data.grammarId === "latex") {
|
|
23
|
+
walkForLatexImports(tree.rootNode, source, imports);
|
|
24
|
+
} else {
|
|
25
|
+
walkForImports(tree.rootNode, source, imports);
|
|
26
|
+
}
|
|
23
27
|
return { kind: "success", data: imports };
|
|
24
28
|
} finally {
|
|
25
29
|
tree.delete();
|
|
@@ -42,3 +46,53 @@ function walkForImports(node: SyntaxNodeLike, source: string, imports: ImportRec
|
|
|
42
46
|
walkForImports(child, source, imports);
|
|
43
47
|
}
|
|
44
48
|
}
|
|
49
|
+
|
|
50
|
+
const LATEX_IMPORT_FIELDS: Readonly<Record<string, string>> = {
|
|
51
|
+
package_include: "paths",
|
|
52
|
+
class_include: "path",
|
|
53
|
+
latex_include: "path",
|
|
54
|
+
biblatex_include: "glob",
|
|
55
|
+
bibstyle_include: "path",
|
|
56
|
+
bibtex_include: "paths",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function walkForLatexImports(node: SyntaxNodeLike, source: string, imports: ImportRecord[]): void {
|
|
60
|
+
if (node.type === "import_include") {
|
|
61
|
+
const directory = groupContent(node.childForFieldName("directory"));
|
|
62
|
+
const file = groupContent(node.childForFieldName("file"));
|
|
63
|
+
if (directory || file) {
|
|
64
|
+
const separator = directory && !directory.endsWith("/") ? "/" : "";
|
|
65
|
+
imports.push({
|
|
66
|
+
moduleSpecifier: `${directory}${separator}${file}`,
|
|
67
|
+
range: nodeToRange(node, source),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const field = LATEX_IMPORT_FIELDS[node.type];
|
|
74
|
+
if (field) {
|
|
75
|
+
for (const moduleSpecifier of fieldValues(node, field)) {
|
|
76
|
+
if (moduleSpecifier) imports.push({ moduleSpecifier, range: nodeToRange(node, source) });
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const child of node.children) {
|
|
82
|
+
walkForLatexImports(child, source, imports);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function fieldValues(node: SyntaxNodeLike, field: string): string[] {
|
|
87
|
+
const value = node.childForFieldName(field);
|
|
88
|
+
if (!value) return [];
|
|
89
|
+
const paths = value.childrenForFieldName("path");
|
|
90
|
+
if (paths.length > 0) return paths.map((path) => path.text.trim()).filter(Boolean);
|
|
91
|
+
return [groupContent(value)];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function groupContent(node: SyntaxNodeLike | null): string {
|
|
95
|
+
if (!node) return "";
|
|
96
|
+
const trimmed = node.text.trim();
|
|
97
|
+
return trimmed.startsWith("{") && trimmed.endsWith("}") ? trimmed.slice(1, -1).trim() : trimmed;
|
|
98
|
+
}
|