@alex-dbs/coderuler 1.5.0 → 1.6.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/dist/adapter/filesystem/rule-test-lsp.d.ts +6 -0
- package/dist/adapter/filesystem/rule-test-lsp.js +189 -0
- package/dist/adapter/filesystem/rule-test-worker.d.ts +4 -0
- package/dist/adapter/filesystem/rule-test-worker.js +48 -28
- package/dist/adapter/filesystem/rule-tests.d.ts +2 -2
- package/dist/adapter/filesystem/rule-tests.js +115 -12
- package/dist/adapter/lsp/backend.d.ts +12 -0
- package/dist/adapter/lsp/backend.js +826 -0
- package/dist/app/concurrency.d.ts +1 -1
- package/dist/app/concurrency.js +12 -8
- package/dist/app/execute.js +75 -13
- package/dist/app/lsp.d.ts +6 -0
- package/dist/app/lsp.js +141 -0
- package/dist/app/ports.d.ts +8 -1
- package/dist/app/rule.d.ts +40 -0
- package/dist/app/rule.js +14 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/infra/runtime.js +14 -3
- package/package.json +2 -1
- package/skills/coderuler-new-rule/reference.md +53 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { MessagePort } from 'node:worker_threads';
|
|
2
|
+
import type { LspBackend, LspBackendFactory, Project } from '../../app/ports.js';
|
|
3
|
+
export declare function createRuleTestLspHost(port: MessagePort, project: Project, createBackend?: LspBackendFactory): {
|
|
4
|
+
close(): Promise<void>;
|
|
5
|
+
};
|
|
6
|
+
export declare function createRuleTestLspBackend(port: MessagePort, signal: AbortSignal): LspBackend;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { createRunLsp } from '../../app/lsp.js';
|
|
2
|
+
function describe(error) {
|
|
3
|
+
return error instanceof Error ? error.message : String(error);
|
|
4
|
+
}
|
|
5
|
+
function send(port, response) {
|
|
6
|
+
try {
|
|
7
|
+
port.postMessage(response);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// A peer can close its port while an operation is settling.
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function createRuleTestLspHost(port, project, createBackend) {
|
|
14
|
+
const facade = createRunLsp(project, createBackend);
|
|
15
|
+
let closed = false;
|
|
16
|
+
let closePromise;
|
|
17
|
+
const onRequest = (value) => {
|
|
18
|
+
if (value === null || typeof value !== 'object')
|
|
19
|
+
return;
|
|
20
|
+
const candidate = value;
|
|
21
|
+
if (!Number.isSafeInteger(candidate.id) || !Array.isArray(candidate.args))
|
|
22
|
+
return;
|
|
23
|
+
const request = value;
|
|
24
|
+
void (async () => {
|
|
25
|
+
try {
|
|
26
|
+
let result;
|
|
27
|
+
switch (request.method) {
|
|
28
|
+
case 'definitions':
|
|
29
|
+
result = await facade.tools.definitions(...request.args);
|
|
30
|
+
break;
|
|
31
|
+
case 'declarations':
|
|
32
|
+
result = await facade.tools.declarations(...request.args);
|
|
33
|
+
break;
|
|
34
|
+
case 'typeDefinitions':
|
|
35
|
+
result = await facade.tools.typeDefinitions(...request.args);
|
|
36
|
+
break;
|
|
37
|
+
case 'implementations':
|
|
38
|
+
result = await facade.tools.implementations(...request.args);
|
|
39
|
+
break;
|
|
40
|
+
case 'references':
|
|
41
|
+
result = await facade.tools.references(...request.args);
|
|
42
|
+
break;
|
|
43
|
+
case 'hover':
|
|
44
|
+
result = await facade.tools.hover(...request.args);
|
|
45
|
+
break;
|
|
46
|
+
case 'documentSymbols':
|
|
47
|
+
result = await facade.tools.documentSymbols(...request.args);
|
|
48
|
+
break;
|
|
49
|
+
case 'workspaceSymbols':
|
|
50
|
+
result = await facade.tools.workspaceSymbols(...request.args);
|
|
51
|
+
break;
|
|
52
|
+
case 'callers':
|
|
53
|
+
result = await facade.tools.callers(...request.args);
|
|
54
|
+
break;
|
|
55
|
+
case 'callees':
|
|
56
|
+
result = await facade.tools.callees(...request.args);
|
|
57
|
+
break;
|
|
58
|
+
case 'close':
|
|
59
|
+
await facade.close();
|
|
60
|
+
result = undefined;
|
|
61
|
+
break;
|
|
62
|
+
default: throw new Error('Unsupported rule-test LSP method');
|
|
63
|
+
}
|
|
64
|
+
send(port, { id: request.id, result });
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
send(port, { id: request.id, error: describe(error) });
|
|
68
|
+
}
|
|
69
|
+
})();
|
|
70
|
+
};
|
|
71
|
+
const close = () => {
|
|
72
|
+
if (closePromise !== undefined)
|
|
73
|
+
return closePromise;
|
|
74
|
+
closed = true;
|
|
75
|
+
port.off('message', onRequest);
|
|
76
|
+
closePromise = (async () => {
|
|
77
|
+
try {
|
|
78
|
+
await facade.close();
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
port.close();
|
|
82
|
+
}
|
|
83
|
+
})();
|
|
84
|
+
return closePromise;
|
|
85
|
+
};
|
|
86
|
+
const onPortClose = () => {
|
|
87
|
+
if (!closed)
|
|
88
|
+
void close().catch(() => undefined);
|
|
89
|
+
};
|
|
90
|
+
port.on('message', onRequest);
|
|
91
|
+
port.once('close', onPortClose);
|
|
92
|
+
port.start();
|
|
93
|
+
return { close };
|
|
94
|
+
}
|
|
95
|
+
export function createRuleTestLspBackend(port, signal) {
|
|
96
|
+
let nextId = 0;
|
|
97
|
+
let portClosed = false;
|
|
98
|
+
let signalAborted = false;
|
|
99
|
+
let closePromise;
|
|
100
|
+
const pending = new Map();
|
|
101
|
+
const rejectPending = (error) => {
|
|
102
|
+
for (const request of pending.values())
|
|
103
|
+
request.reject(error);
|
|
104
|
+
pending.clear();
|
|
105
|
+
};
|
|
106
|
+
const onResponse = (value) => {
|
|
107
|
+
if (value === null || typeof value !== 'object')
|
|
108
|
+
return;
|
|
109
|
+
const candidate = value;
|
|
110
|
+
if (!Number.isSafeInteger(candidate.id))
|
|
111
|
+
return;
|
|
112
|
+
const response = value;
|
|
113
|
+
const request = pending.get(response.id);
|
|
114
|
+
if (request === undefined)
|
|
115
|
+
return;
|
|
116
|
+
pending.delete(response.id);
|
|
117
|
+
if ('error' in response)
|
|
118
|
+
request.reject(new Error(response.error));
|
|
119
|
+
else
|
|
120
|
+
request.resolve(response.result);
|
|
121
|
+
};
|
|
122
|
+
const onPortClose = () => {
|
|
123
|
+
if (portClosed)
|
|
124
|
+
return;
|
|
125
|
+
portClosed = true;
|
|
126
|
+
rejectPending(new Error('Rule test LSP host closed'));
|
|
127
|
+
};
|
|
128
|
+
const onAbort = () => {
|
|
129
|
+
signalAborted = true;
|
|
130
|
+
rejectPending(signal.reason instanceof Error ? signal.reason : new Error('Rule test LSP backend aborted'));
|
|
131
|
+
};
|
|
132
|
+
port.on('message', onResponse);
|
|
133
|
+
port.once('close', onPortClose);
|
|
134
|
+
port.start();
|
|
135
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
136
|
+
if (signal.aborted)
|
|
137
|
+
onAbort();
|
|
138
|
+
const request = (method, args, allowAborted = false) => {
|
|
139
|
+
if (portClosed)
|
|
140
|
+
return Promise.reject(new Error('Rule test LSP host closed'));
|
|
141
|
+
if (signalAborted && !allowAborted) {
|
|
142
|
+
return Promise.reject(signal.reason instanceof Error ? signal.reason : new Error('Rule test LSP backend aborted'));
|
|
143
|
+
}
|
|
144
|
+
const id = nextId++;
|
|
145
|
+
const deferred = Promise.withResolvers();
|
|
146
|
+
pending.set(id, {
|
|
147
|
+
resolve: value => deferred.resolve(value),
|
|
148
|
+
reject: deferred.reject,
|
|
149
|
+
});
|
|
150
|
+
try {
|
|
151
|
+
port.postMessage({ id, method, args });
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
pending.delete(id);
|
|
155
|
+
deferred.reject(error instanceof Error ? error : new Error(describe(error)));
|
|
156
|
+
}
|
|
157
|
+
return deferred.promise;
|
|
158
|
+
};
|
|
159
|
+
const close = () => {
|
|
160
|
+
if (closePromise !== undefined)
|
|
161
|
+
return closePromise;
|
|
162
|
+
closePromise = (async () => {
|
|
163
|
+
try {
|
|
164
|
+
await request('close', [], true);
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
signal.removeEventListener('abort', onAbort);
|
|
168
|
+
port.off('message', onResponse);
|
|
169
|
+
port.off('close', onPortClose);
|
|
170
|
+
port.close();
|
|
171
|
+
onPortClose();
|
|
172
|
+
}
|
|
173
|
+
})();
|
|
174
|
+
return closePromise;
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
definitions: (target, options) => request('definitions', [target, options]),
|
|
178
|
+
declarations: (target, options) => request('declarations', [target, options]),
|
|
179
|
+
typeDefinitions: (target, options) => request('typeDefinitions', [target, options]),
|
|
180
|
+
implementations: (target, options) => request('implementations', [target, options]),
|
|
181
|
+
references: (target, options) => request('references', [target, options]),
|
|
182
|
+
hover: (target, options) => request('hover', [target, options]),
|
|
183
|
+
documentSymbols: (filepath, options) => request('documentSymbols', [filepath, options]),
|
|
184
|
+
workspaceSymbols: (query, options) => request('workspaceSymbols', [query, options]),
|
|
185
|
+
callers: (target, options) => request('callers', [target, options]),
|
|
186
|
+
callees: (target, options) => request('callees', [target, options]),
|
|
187
|
+
close,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { type MessagePort } from 'node:worker_threads';
|
|
1
2
|
import type { RuleTestCase } from '../../app/rule-tests.js';
|
|
2
3
|
import type { PreparedRuleDependencies } from './dependencies.js';
|
|
3
4
|
export interface RuleTestWorkerRequest {
|
|
4
5
|
readonly id: number;
|
|
6
|
+
readonly lspPort: MessagePort;
|
|
7
|
+
readonly rubyLspPort: MessagePort;
|
|
8
|
+
readonly typescriptLspPort: MessagePort;
|
|
5
9
|
readonly root: string;
|
|
6
10
|
readonly ruleFile: string;
|
|
7
11
|
readonly files: RuleTestCase['files'];
|
|
@@ -3,6 +3,7 @@ import { dirname, isAbsolute, resolve, sep, win32 } from 'node:path';
|
|
|
3
3
|
import { parentPort } from 'node:worker_threads';
|
|
4
4
|
import { executeRules } from '../../app/execute.js';
|
|
5
5
|
import { createDecisionResolver } from '../../infra/settings.js';
|
|
6
|
+
import { createRuleTestLspBackend } from './rule-test-lsp.js';
|
|
6
7
|
import { createProject } from './project.js';
|
|
7
8
|
import { createRuleLoader } from './rules.js';
|
|
8
9
|
function fixturePath(root, filepath) {
|
|
@@ -12,37 +13,56 @@ function fixturePath(root, filepath) {
|
|
|
12
13
|
}
|
|
13
14
|
return target;
|
|
14
15
|
}
|
|
15
|
-
async function runCase(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
async function runCase(request) {
|
|
17
|
+
const { lspPort, rubyLspPort, typescriptLspPort, root, ruleFile, files, ai, dependencies, includeDisabled } = request;
|
|
18
|
+
try {
|
|
19
|
+
for (const [filepath, source] of Object.entries(files)) {
|
|
20
|
+
const target = fixturePath(root, filepath);
|
|
21
|
+
await mkdir(dirname(target), { recursive: true });
|
|
22
|
+
await writeFile(target, typeof source === 'string' ? source : JSON.stringify(source, null, 2), 'utf8');
|
|
23
|
+
}
|
|
24
|
+
const rules = await createRuleLoader(dependencies === undefined ? {} : { preparedDependencies: dependencies }).load({ root, explicit: ruleFile, invocationCwd: root });
|
|
25
|
+
if (rules.length !== 1)
|
|
26
|
+
throw new Error('Expected exactly one freshly loaded rule');
|
|
27
|
+
if (includeDisabled === true)
|
|
28
|
+
rules[0].rule.enabled = true;
|
|
29
|
+
const reports = await executeRules(rules, {
|
|
30
|
+
project: createProject(root, { excludeFiles: [ruleFile] }),
|
|
31
|
+
isTarget: () => true,
|
|
32
|
+
concurrency: 1,
|
|
33
|
+
resolveDecision: ai ? createDecisionResolver(process.env) : () => {
|
|
34
|
+
throw new Error('Inline rule tests must not call the decision backend');
|
|
35
|
+
},
|
|
36
|
+
createLspBackend: (_project, signal) => createRuleTestLspBackend(lspPort, signal),
|
|
37
|
+
createRubyLspBackend: (_project, signal) => createRuleTestLspBackend(rubyLspPort, signal),
|
|
38
|
+
createTypescriptLspBackend: (_project, signal) => createRuleTestLspBackend(typescriptLspPort, signal),
|
|
39
|
+
});
|
|
40
|
+
const report = reports[0];
|
|
41
|
+
if (reports.length !== 1 || report === undefined)
|
|
42
|
+
throw new Error('Expected exactly one rule report');
|
|
43
|
+
let aiScores;
|
|
44
|
+
if (ai) {
|
|
45
|
+
for (const evaluation of report.evaluations) {
|
|
46
|
+
const score = evaluation.result.probability;
|
|
47
|
+
if (score !== undefined)
|
|
48
|
+
(aiScores ??= []).push(score);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { actual: report.status, ...(aiScores === undefined ? {} : { aiScores }) };
|
|
20
52
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
const report = reports[0];
|
|
35
|
-
if (reports.length !== 1 || report === undefined)
|
|
36
|
-
throw new Error('Expected exactly one rule report');
|
|
37
|
-
let aiScores;
|
|
38
|
-
if (ai) {
|
|
39
|
-
for (const evaluation of report.evaluations) {
|
|
40
|
-
const score = evaluation.result.probability;
|
|
41
|
-
if (score !== undefined)
|
|
42
|
-
(aiScores ??= []).push(score);
|
|
53
|
+
finally {
|
|
54
|
+
try {
|
|
55
|
+
lspPort.close();
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
try {
|
|
59
|
+
rubyLspPort.close();
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
typescriptLspPort.close();
|
|
63
|
+
}
|
|
43
64
|
}
|
|
44
65
|
}
|
|
45
|
-
return { actual: report.status, ...(aiScores === undefined ? {} : { aiScores }) };
|
|
46
66
|
}
|
|
47
67
|
const port = parentPort;
|
|
48
68
|
if (port === null)
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { LoadedRule } from '../../app/ports.js';
|
|
1
|
+
import type { LoadedRule, LspBackendFactory } from '../../app/ports.js';
|
|
2
2
|
import type { RuleTestResult } from '../../app/rule-tests.js';
|
|
3
|
-
export declare function runRuleTests(rules: readonly LoadedRule[], onResult?: (result: RuleTestResult, lastInRule: boolean) => void, timeoutMs?: number, concurrency?: number, includeAI?: boolean, includeDisabled?: boolean): Promise<readonly RuleTestResult[]>;
|
|
3
|
+
export declare function runRuleTests(rules: readonly LoadedRule[], onResult?: (result: RuleTestResult, lastInRule: boolean) => void, timeoutMs?: number, concurrency?: number, includeAI?: boolean, includeDisabled?: boolean, createLspBackend?: LspBackendFactory, createRubyLspBackend?: LspBackendFactory, createTypescriptLspBackend?: LspBackendFactory): Promise<readonly RuleTestResult[]>;
|
|
@@ -2,8 +2,10 @@ import { lstat, mkdtemp, realpath, rm } from 'node:fs/promises';
|
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
|
-
import { Worker } from 'node:worker_threads';
|
|
5
|
+
import { MessageChannel, Worker } from 'node:worker_threads';
|
|
6
6
|
import { isRecord, validateJsonValue } from '../../app/validate.js';
|
|
7
|
+
import { createRuleTestLspHost } from './rule-test-lsp.js';
|
|
8
|
+
import { createProject } from './project.js';
|
|
7
9
|
import { getRuleDependencies, registerRuleDependencies } from './rule-resolution.js';
|
|
8
10
|
// A JavaScript entry lets inherited tsx hooks resolve the source worker's .js import.
|
|
9
11
|
const WORKER_MODULE = new URL('./rule-test-worker.js', import.meta.url);
|
|
@@ -61,6 +63,7 @@ class RuleTestWorkerSession {
|
|
|
61
63
|
onFailure;
|
|
62
64
|
terminating = false;
|
|
63
65
|
termination;
|
|
66
|
+
activeLsp;
|
|
64
67
|
onError = (error) => {
|
|
65
68
|
if (this.terminating)
|
|
66
69
|
return;
|
|
@@ -83,12 +86,41 @@ class RuleTestWorkerSession {
|
|
|
83
86
|
if (this.failure !== undefined)
|
|
84
87
|
throw this.failure;
|
|
85
88
|
}
|
|
86
|
-
|
|
89
|
+
closeLsp(active = this.activeLsp) {
|
|
90
|
+
if (active === undefined)
|
|
91
|
+
return Promise.resolve();
|
|
92
|
+
if (active.closePromise !== undefined)
|
|
93
|
+
return active.closePromise;
|
|
94
|
+
active.closePromise = (async () => {
|
|
95
|
+
try {
|
|
96
|
+
const hosts = [active.go.host, active.ruby.host, active.typescript.host].filter((host) => host !== undefined);
|
|
97
|
+
const results = await Promise.allSettled(hosts.map(async (host) => host.close()));
|
|
98
|
+
const rejected = results.find(result => result.status === 'rejected');
|
|
99
|
+
if (rejected?.status === 'rejected')
|
|
100
|
+
throw rejected.reason;
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
active.go.channel.port1.close();
|
|
104
|
+
active.ruby.channel.port1.close();
|
|
105
|
+
active.typescript.channel.port1.close();
|
|
106
|
+
if (!active.transferred) {
|
|
107
|
+
active.go.channel.port2.close();
|
|
108
|
+
active.ruby.channel.port2.close();
|
|
109
|
+
active.typescript.channel.port2.close();
|
|
110
|
+
}
|
|
111
|
+
if (this.activeLsp === active)
|
|
112
|
+
this.activeLsp = undefined;
|
|
113
|
+
}
|
|
114
|
+
})();
|
|
115
|
+
return active.closePromise;
|
|
116
|
+
}
|
|
117
|
+
async run(request, deadline, onFailure, createBackend, createRubyLspBackend, createTypescriptLspBackend) {
|
|
87
118
|
this.onFailure = onFailure;
|
|
88
119
|
this.assertHealthy();
|
|
89
120
|
const id = this.nextId++;
|
|
90
121
|
const result = Promise.withResolvers();
|
|
91
122
|
let timer;
|
|
123
|
+
let activeLsp;
|
|
92
124
|
const expire = () => {
|
|
93
125
|
const remaining = deadline - performance.now();
|
|
94
126
|
if (remaining <= 0)
|
|
@@ -112,29 +144,94 @@ class RuleTestWorkerSession {
|
|
|
112
144
|
else
|
|
113
145
|
result.reject(error);
|
|
114
146
|
};
|
|
147
|
+
let operationFailed = false;
|
|
115
148
|
this.worker.on('message', onMessage);
|
|
116
149
|
try {
|
|
117
150
|
expire();
|
|
118
151
|
if (performance.now() < deadline) {
|
|
119
|
-
|
|
152
|
+
const channels = [];
|
|
153
|
+
try {
|
|
154
|
+
const goChannel = new MessageChannel();
|
|
155
|
+
channels.push(goChannel);
|
|
156
|
+
const rubyChannel = new MessageChannel();
|
|
157
|
+
channels.push(rubyChannel);
|
|
158
|
+
const typescriptChannel = new MessageChannel();
|
|
159
|
+
channels.push(typescriptChannel);
|
|
160
|
+
activeLsp = {
|
|
161
|
+
go: { channel: goChannel, host: undefined },
|
|
162
|
+
ruby: { channel: rubyChannel, host: undefined },
|
|
163
|
+
typescript: { channel: typescriptChannel, host: undefined },
|
|
164
|
+
transferred: false,
|
|
165
|
+
};
|
|
166
|
+
this.activeLsp = activeLsp;
|
|
167
|
+
const project = createProject(request.root, { excludeFiles: [request.ruleFile] });
|
|
168
|
+
activeLsp.go.host = createRuleTestLspHost(goChannel.port1, project, createBackend);
|
|
169
|
+
activeLsp.ruby.host = createRuleTestLspHost(rubyChannel.port1, project, createRubyLspBackend);
|
|
170
|
+
activeLsp.typescript.host = createRuleTestLspHost(typescriptChannel.port1, project, createTypescriptLspBackend);
|
|
171
|
+
this.worker.postMessage({
|
|
172
|
+
...request, id, lspPort: goChannel.port2, rubyLspPort: rubyChannel.port2,
|
|
173
|
+
typescriptLspPort: typescriptChannel.port2,
|
|
174
|
+
}, [goChannel.port2, rubyChannel.port2, typescriptChannel.port2]);
|
|
175
|
+
activeLsp.transferred = true;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (activeLsp === undefined) {
|
|
179
|
+
for (const channel of channels) {
|
|
180
|
+
channel.port1.close();
|
|
181
|
+
channel.port2.close();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
120
186
|
}
|
|
121
187
|
return await result.promise;
|
|
122
188
|
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
operationFailed = true;
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
123
193
|
finally {
|
|
124
194
|
clearTimeout(timer);
|
|
125
195
|
this.worker.off('message', onMessage);
|
|
126
196
|
this.rejectPending = undefined;
|
|
197
|
+
try {
|
|
198
|
+
await this.closeLsp(activeLsp);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
if (!operationFailed)
|
|
202
|
+
throw error;
|
|
203
|
+
if (activeLsp !== undefined)
|
|
204
|
+
activeLsp.closePromise = Promise.resolve();
|
|
205
|
+
}
|
|
127
206
|
}
|
|
128
207
|
}
|
|
129
208
|
async stop() {
|
|
130
209
|
if (this.termination === undefined) {
|
|
131
210
|
this.terminating = true;
|
|
132
211
|
this.rejectPending?.(new Error('Rule test worker stopped'));
|
|
133
|
-
this.termination =
|
|
212
|
+
this.termination = (async () => {
|
|
213
|
+
let closeFailed = false;
|
|
214
|
+
let closeError;
|
|
215
|
+
try {
|
|
216
|
+
await this.closeLsp();
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
closeFailed = true;
|
|
220
|
+
closeError = error;
|
|
221
|
+
}
|
|
222
|
+
const code = await this.worker.terminate();
|
|
223
|
+
if (closeFailed)
|
|
224
|
+
throw closeError;
|
|
225
|
+
return code;
|
|
226
|
+
})();
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
await this.termination;
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
this.worker.off('error', this.onError);
|
|
233
|
+
this.worker.off('exit', this.onExit);
|
|
134
234
|
}
|
|
135
|
-
await this.termination;
|
|
136
|
-
this.worker.off('error', this.onError);
|
|
137
|
-
this.worker.off('exit', this.onExit);
|
|
138
235
|
}
|
|
139
236
|
}
|
|
140
237
|
async function stopSession(session) {
|
|
@@ -143,7 +240,7 @@ async function stopSession(session) {
|
|
|
143
240
|
if (session.current === current)
|
|
144
241
|
session.current = undefined;
|
|
145
242
|
}
|
|
146
|
-
async function runCase(entry, example, ai, timeoutMs, session, onFailure, includeDisabled) {
|
|
243
|
+
async function runCase(entry, example, ai, timeoutMs, session, onFailure, includeDisabled, createLspBackend, createRubyLspBackend, createTypescriptLspBackend) {
|
|
147
244
|
const started = performance.now();
|
|
148
245
|
const deadline = started + timeoutMs;
|
|
149
246
|
let failure;
|
|
@@ -172,7 +269,7 @@ async function runCase(entry, example, ai, timeoutMs, session, onFailure, includ
|
|
|
172
269
|
root, ruleFile: entry.ruleFile, files: example.files, ai,
|
|
173
270
|
...(includeDisabled ? { includeDisabled: true } : {}),
|
|
174
271
|
...(dependencies === undefined ? {} : { dependencies }),
|
|
175
|
-
}, deadline, failCase);
|
|
272
|
+
}, deadline, failCase, createLspBackend, createRubyLspBackend, createTypescriptLspBackend);
|
|
176
273
|
if (typeof outcome === 'string')
|
|
177
274
|
actual = outcome;
|
|
178
275
|
else {
|
|
@@ -210,7 +307,7 @@ async function runCase(entry, example, ai, timeoutMs, session, onFailure, includ
|
|
|
210
307
|
throw cause;
|
|
211
308
|
}
|
|
212
309
|
}
|
|
213
|
-
export async function runRuleTests(rules, onResult, timeoutMs = 10_000, concurrency = 1, includeAI = false, includeDisabled = false) {
|
|
310
|
+
export async function runRuleTests(rules, onResult, timeoutMs = 10_000, concurrency = 1, includeAI = false, includeDisabled = false, createLspBackend, createRubyLspBackend, createTypescriptLspBackend) {
|
|
214
311
|
if (!Number.isSafeInteger(concurrency) || concurrency < 1) {
|
|
215
312
|
throw new RangeError(`--concurrency must be a positive safe integer, received ${String(concurrency)}`);
|
|
216
313
|
}
|
|
@@ -273,7 +370,7 @@ export async function runRuleTests(rules, onResult, timeoutMs = 10_000, concurre
|
|
|
273
370
|
}
|
|
274
371
|
const group = groups[groupIndex];
|
|
275
372
|
const example = validateCase(group.values[caseIndex], `${group.label}[${caseIndex}]`);
|
|
276
|
-
completed[index] = await runCase(rule, example, group.ai, timeoutMs, session, fail, includeDisabled);
|
|
373
|
+
completed[index] = await runCase(rule, example, group.ai, timeoutMs, session, fail, includeDisabled, createLspBackend, createRubyLspBackend, createTypescriptLspBackend);
|
|
277
374
|
if (failureObserved)
|
|
278
375
|
return;
|
|
279
376
|
while (completed[nextResult] !== undefined) {
|
|
@@ -307,6 +404,12 @@ export async function runRuleTests(rules, onResult, timeoutMs = 10_000, concurre
|
|
|
307
404
|
}
|
|
308
405
|
finally {
|
|
309
406
|
await cancellation;
|
|
310
|
-
|
|
407
|
+
try {
|
|
408
|
+
await Promise.all(sessions.map(stopSession));
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
if (!failureObserved)
|
|
412
|
+
throw error;
|
|
413
|
+
}
|
|
311
414
|
}
|
|
312
415
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { LspBackend, Project } from '../../app/ports.js';
|
|
2
|
+
export interface LspServerOptions {
|
|
3
|
+
readonly command: string;
|
|
4
|
+
readonly args?: readonly string[];
|
|
5
|
+
readonly languageId: string;
|
|
6
|
+
readonly initializationProgressToken?: string | number;
|
|
7
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
8
|
+
readonly requestTimeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function createLspBackend(project: Project, signal: AbortSignal, options?: LspServerOptions): Promise<LspBackend>;
|
|
11
|
+
export declare function createRubyLspBackend(project: Project, signal: AbortSignal): Promise<LspBackend>;
|
|
12
|
+
export declare function createTypescriptLspBackend(project: Project, signal: AbortSignal): Promise<LspBackend>;
|