gennady 0.7.0 → 0.7.1
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/chunks/_commonjsHelpers-DaMA6jEr.js +8 -0
- package/dist/chunks/{devtools-B-7ugZhF.js → devtools-CPZ0IUNM.js} +1 -1
- package/dist/chunks/{index-CaahtXiM.js → index-BCqnKvNZ.js} +2 -2
- package/dist/chunks/{index-BeL1Zcbg.js → index-BLsDffzZ.js} +1 -1
- package/dist/chunks/{index-CVR66voe.js → index-Bq4d25CK.js} +17 -16
- package/dist/chunks/{index-D1qsi0Uc.js → index-BrSutH7l.js} +3 -3
- package/dist/chunks/{index-UbHoePfr.js → index-BsD-J44e.js} +1 -1
- package/dist/chunks/{index-g1LXlp77.js → index-BzBhCTHq.js} +2 -2
- package/dist/chunks/{index-DU6jD7SS.js → index-CAWs-GED.js} +1 -1
- package/dist/chunks/{index-CXuhZzS3.js → index-CFd_iufb.js} +1 -1
- package/dist/chunks/{services-CaLOhuLV.js → index-DAU6h0aT.js} +132 -2843
- package/dist/chunks/{index-DJpVmyp2.js → index-Db09OODV.js} +2 -2
- package/dist/chunks/{index-CiEM-8nJ.js → index-Dc44ukmL.js} +2 -2
- package/dist/chunks/{index-zBnnuvLA.js → index-DeYzAoxF.js} +7 -6
- package/dist/chunks/{index-D9ceRUyB.js → index-O5kqC9i_.js} +2 -2
- package/dist/chunks/{index-KxSZKmZn.js → index-TX0TmKeu.js} +2 -2
- package/dist/chunks/{run-review-command.logic-DpkRoEi8.js → run-review-command.logic-CwxYeLby.js} +2 -2
- package/dist/chunks/services-Dnk3nB2x.js +2732 -0
- package/dist/chunks/{shared-Bjy30TeM.js → shared-COpFaV-w.js} +1 -1
- package/dist/gennady.js +14 -14
- package/dist/index.js +5 -5
- package/dist/services/dbc/linter/implementations/ts/dbc-ts-ast-adapter.d.ts +3 -2
- package/package.json +1 -1
|
@@ -0,0 +1,2732 @@
|
|
|
1
|
+
import { createServer as ie } from "node:http";
|
|
2
|
+
import { readFileSync as A, writeFileSync as oe, existsSync as K, readdirSync as X, statSync as ae } from "node:fs";
|
|
3
|
+
import * as M from "node:path";
|
|
4
|
+
import { join as $ } from "node:path";
|
|
5
|
+
import * as ce from "node:os";
|
|
6
|
+
import { homedir as D } from "node:os";
|
|
7
|
+
import { execSync as le } from "node:child_process";
|
|
8
|
+
import { DatabaseSync as de } from "node:sqlite";
|
|
9
|
+
const S = { debug: 0, info: 1, warn: 2, error: 3, silent: 4 };
|
|
10
|
+
let E = "warn";
|
|
11
|
+
const mt = (i) => {
|
|
12
|
+
E = i;
|
|
13
|
+
}, g = {
|
|
14
|
+
debug: (i, e) => {
|
|
15
|
+
S[E] <= S.debug && console.debug(i, ...e !== void 0 ? [e] : []);
|
|
16
|
+
},
|
|
17
|
+
info: (i, e) => {
|
|
18
|
+
S[E] <= S.info && console.info(i, ...e !== void 0 ? [e] : []);
|
|
19
|
+
},
|
|
20
|
+
warn: (i, e) => {
|
|
21
|
+
S[E] <= S.warn && console.warn(i, ...e !== void 0 ? [e] : []);
|
|
22
|
+
},
|
|
23
|
+
error: (i, e) => {
|
|
24
|
+
S[E] <= S.error && console.error(i, ...e !== void 0 ? [e] : []);
|
|
25
|
+
}
|
|
26
|
+
}, gt = (i) => {
|
|
27
|
+
if (!i || typeof i != "string" || i.trim().length === 0)
|
|
28
|
+
return null;
|
|
29
|
+
const e = i.trim();
|
|
30
|
+
let t;
|
|
31
|
+
try {
|
|
32
|
+
t = new URL(e);
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const r = t.host, n = t.pathname.replace(/\/$/, ""), s = n.match(/^\/(.+?)\/-\/merge_requests\/(\d+)$/);
|
|
37
|
+
if (s) {
|
|
38
|
+
const a = parseInt(s[2], 10);
|
|
39
|
+
return isNaN(a) ? null : {
|
|
40
|
+
provider: "gitlab",
|
|
41
|
+
host: r,
|
|
42
|
+
repository: s[1],
|
|
43
|
+
iid: a
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const o = n.match(/^\/(.+?)\/pull\/(\d+)$/);
|
|
47
|
+
if (o) {
|
|
48
|
+
const a = parseInt(o[2], 10);
|
|
49
|
+
return isNaN(a) ? null : {
|
|
50
|
+
provider: "github",
|
|
51
|
+
host: r,
|
|
52
|
+
repository: o[1],
|
|
53
|
+
iid: a
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
};
|
|
58
|
+
class Y {
|
|
59
|
+
}
|
|
60
|
+
class ue extends Y {
|
|
61
|
+
/** @purpose Bound HTTP request function injected for GitLab API calls */
|
|
62
|
+
_request;
|
|
63
|
+
/**
|
|
64
|
+
* @purpose Wire the HTTP request adapter for GitLab merge request endpoints.
|
|
65
|
+
* @param request Authenticated HTTP request function targeting GitLab API.
|
|
66
|
+
*/
|
|
67
|
+
constructor(e) {
|
|
68
|
+
super(), this._request = e;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* @param query Target project and optional filters.
|
|
72
|
+
* @returns List of merge requests matching the filters.
|
|
73
|
+
* @sideEffect Network: GET /projects/:project/merge_requests
|
|
74
|
+
* @see {VcsClientMergeRequests#getList} in services/vcs-client/abstract/vcs-merge-requests.ts
|
|
75
|
+
*/
|
|
76
|
+
async getList(e) {
|
|
77
|
+
const t = new URLSearchParams(), r = e?.state ?? "opened";
|
|
78
|
+
e?.sourceBranch && t.set("source_branch", e.sourceBranch), r && t.set("state", r), e?.perPage && t.set("per_page", String(e.perPage)), e?.page && t.set("page", String(e.page));
|
|
79
|
+
const n = encodeURIComponent(e.project), s = await this._request(
|
|
80
|
+
`/projects/${n}/merge_requests?${t.toString()}`
|
|
81
|
+
);
|
|
82
|
+
return Array.isArray(s) ? s : [];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* @param query Target project and optional filters.
|
|
86
|
+
* @returns First matching merge request or null.
|
|
87
|
+
* @sideEffect Network: Delegates to getList() with per_page=1 limit.
|
|
88
|
+
* @see {VcsClientMergeRequests#getList} in services/vcs-client/abstract/vcs-merge-requests.ts
|
|
89
|
+
*/
|
|
90
|
+
async getOne(e) {
|
|
91
|
+
const t = await this.getList({ ...e, perPage: 1 });
|
|
92
|
+
return t.length > 0 ? t[0] : null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* @param query Target project and MR IID.
|
|
96
|
+
* @returns Merge request object or null on 404.
|
|
97
|
+
* @sideEffect Network: GET /projects/:project/merge_requests/:iid
|
|
98
|
+
* @see {VcsClientMergeRequests#getList} in services/vcs-client/abstract/vcs-merge-requests.ts
|
|
99
|
+
*/
|
|
100
|
+
async getByIid(e) {
|
|
101
|
+
const t = encodeURIComponent(e.project), r = encodeURIComponent(String(e.iid));
|
|
102
|
+
try {
|
|
103
|
+
return await this._request(`/projects/${t}/merge_requests/${r}`);
|
|
104
|
+
} catch (n) {
|
|
105
|
+
if ((n.message ?? "").includes("404"))
|
|
106
|
+
return null;
|
|
107
|
+
throw n;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* @param query Target repository and MR IID, optional pagination.
|
|
112
|
+
* @returns List of changed files with metadata.
|
|
113
|
+
* @sideEffect Network: GET /projects/:project/merge_requests/:iid/changes
|
|
114
|
+
* @see {VcsClientMergeRequests#getChanges} in services/vcs-client/abstract/vcs-client-merge-requests.ts
|
|
115
|
+
*/
|
|
116
|
+
async getChanges(e) {
|
|
117
|
+
const t = new URLSearchParams();
|
|
118
|
+
e?.perPage && t.set("per_page", String(e.perPage)), e?.page && t.set("page", String(e.page));
|
|
119
|
+
const r = encodeURIComponent(e.repository), n = encodeURIComponent(String(e.iid)), s = await this._request(
|
|
120
|
+
`/projects/${r}/merge_requests/${n}/changes?${t.toString()}`
|
|
121
|
+
), o = s?.changes ?? [], a = s.sha ?? s.source_branch ?? "main";
|
|
122
|
+
return o.map((c) => {
|
|
123
|
+
const d = c.new_path ?? c.old_path ?? "", l = c.new_file, u = c.deleted_file, h = c.renamed_file;
|
|
124
|
+
let p = "modified";
|
|
125
|
+
u ? p = "deleted" : l ? p = "added" : h && (p = "renamed");
|
|
126
|
+
const f = {
|
|
127
|
+
path: d,
|
|
128
|
+
status: p,
|
|
129
|
+
ref: a
|
|
130
|
+
};
|
|
131
|
+
return h && c.old_path && (f.previousPath = c.old_path), typeof c.additions == "number" && (f.additions = c.additions), typeof c.deletions == "number" && (f.deletions = c.deletions), f;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
class pe {
|
|
136
|
+
}
|
|
137
|
+
class fe extends pe {
|
|
138
|
+
/** @purpose Bound HTTP request function injected for GitLab API calls */
|
|
139
|
+
_request;
|
|
140
|
+
/**
|
|
141
|
+
* @purpose Wire the HTTP request adapter for GitLab merge discussion endpoints.
|
|
142
|
+
* @param request Authenticated HTTP request function targeting GitLab API.
|
|
143
|
+
*/
|
|
144
|
+
constructor(e) {
|
|
145
|
+
super(), this._request = e;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* @param query Target project, MR, discussion, and note body.
|
|
149
|
+
* @returns Created note object from GitLab API.
|
|
150
|
+
* @sideEffect Network: POST /projects/:project/merge_requests/:iid/discussions/:discussion_id/notes
|
|
151
|
+
* @see {VcsClientMergeDiscussions#addNote} in services/vcs-client/abstract/vcs-merge-discussions.ts
|
|
152
|
+
*/
|
|
153
|
+
async addNote(e) {
|
|
154
|
+
const t = encodeURIComponent(e.project), r = encodeURIComponent(String(e.iid)), n = encodeURIComponent(e.discussionId);
|
|
155
|
+
return this._request(
|
|
156
|
+
`/projects/${t}/merge_requests/${r}/discussions/${n}/notes`,
|
|
157
|
+
{
|
|
158
|
+
method: "POST",
|
|
159
|
+
headers: { "Content-Type": "application/json" },
|
|
160
|
+
body: JSON.stringify({ body: e.body })
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* @param query Target project, MR, and optional pagination.
|
|
166
|
+
* @returns List of discussions for the requested page.
|
|
167
|
+
* @sideEffect Network: GET /projects/:project/merge_requests/:iid/discussions
|
|
168
|
+
* @see {VcsClientMergeDiscussions#getList} in services/vcs-client/abstract/vcs-merge-discussions.ts
|
|
169
|
+
*/
|
|
170
|
+
async getList(e) {
|
|
171
|
+
const t = new URLSearchParams();
|
|
172
|
+
e?.perPage && t.set("per_page", String(e.perPage)), e?.page && t.set("page", String(e.page));
|
|
173
|
+
const r = encodeURIComponent(e.project), n = await this._request(
|
|
174
|
+
`/projects/${r}/merge_requests/${encodeURIComponent(String(e.iid))}/discussions?${t.toString()}`
|
|
175
|
+
);
|
|
176
|
+
return Array.isArray(n) ? n : [];
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* @param query Target project and MR.
|
|
180
|
+
* @returns Complete list of all discussions across all pages.
|
|
181
|
+
* @sideEffect Network: Multiple GET requests for paginated loading.
|
|
182
|
+
* @see {VcsClientMergeDiscussions#getAll} in services/vcs-client/abstract/vcs-merge-discussions.ts
|
|
183
|
+
*/
|
|
184
|
+
async getAll(e) {
|
|
185
|
+
let r = 1;
|
|
186
|
+
const n = [];
|
|
187
|
+
for (; ; ) {
|
|
188
|
+
const s = await this.getList({ ...e, perPage: 100, page: r });
|
|
189
|
+
if (!Array.isArray(s) || s.length === 0 || (n.push(...s), s.length < 100)) break;
|
|
190
|
+
r += 1;
|
|
191
|
+
}
|
|
192
|
+
return n;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
class Q {
|
|
196
|
+
}
|
|
197
|
+
class he extends Q {
|
|
198
|
+
/** @purpose GitLab instance base URL */
|
|
199
|
+
_baseUrl;
|
|
200
|
+
/** @purpose GitLab access token */
|
|
201
|
+
_token;
|
|
202
|
+
/**
|
|
203
|
+
* @purpose Wire the adapter with GitLab connection params for raw file fetches.
|
|
204
|
+
* @param baseUrl GitLab instance base URL (e.g. https://gitlab.com/api/v4).
|
|
205
|
+
* @param token GitLab personal access token.
|
|
206
|
+
*/
|
|
207
|
+
constructor(e, t) {
|
|
208
|
+
super(), this._baseUrl = e, this._token = t;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* @param query Target repository, file path, and ref.
|
|
212
|
+
* @returns File content or null if file not found.
|
|
213
|
+
* @sideEffect Network: GET /projects/:id/repository/files/:path/raw?ref=
|
|
214
|
+
* @see {VcsClientRepositoryFiles#getFileContent} in services/vcs-client/abstract/vcs-client-repository-files.ts
|
|
215
|
+
*/
|
|
216
|
+
async getFileContent(e) {
|
|
217
|
+
const t = encodeURIComponent(e.repository), r = encodeURIComponent(e.path), n = encodeURIComponent(e.ref);
|
|
218
|
+
let s;
|
|
219
|
+
try {
|
|
220
|
+
s = await fetch(
|
|
221
|
+
`${this._baseUrl}/projects/${t}/repository/files/${r}/raw?ref=${n}`,
|
|
222
|
+
{ headers: { "PRIVATE-TOKEN": this._token } }
|
|
223
|
+
);
|
|
224
|
+
} catch (d) {
|
|
225
|
+
throw new Error(`GitLab request failed: network error for file ${e.path}`, { cause: d });
|
|
226
|
+
}
|
|
227
|
+
if (s.status === 404)
|
|
228
|
+
return null;
|
|
229
|
+
if (!s.ok) {
|
|
230
|
+
const d = await s.text().catch(() => "");
|
|
231
|
+
throw new Error(`GitLab request failed: ${s.status} ${s.statusText} ${d}`);
|
|
232
|
+
}
|
|
233
|
+
const o = s.headers.get("content-type") ?? "", a = await s.text(), c = o.startsWith("text/") || o.includes("json") || o.includes("xml") || o.includes("javascript") || !o;
|
|
234
|
+
return {
|
|
235
|
+
path: e.path,
|
|
236
|
+
content: a,
|
|
237
|
+
encoding: c ? "utf-8" : "base64"
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
class Z {
|
|
242
|
+
}
|
|
243
|
+
class _t extends Z {
|
|
244
|
+
/** @see {VcsClient#MergeRequests} in services/vcs-client/abstract/vcs-client.ts */
|
|
245
|
+
MergeRequests;
|
|
246
|
+
/** @see {VcsClient#MergeDiscussions} in services/vcs-client/abstract/vcs-client.ts */
|
|
247
|
+
MergeDiscussions;
|
|
248
|
+
/** @see {VcsClient#RepositoryFiles} in services/vcs-client/abstract/vcs-client.ts */
|
|
249
|
+
RepositoryFiles;
|
|
250
|
+
/**
|
|
251
|
+
* @purpose Create a GitLab API client bound to a base URL with access token.
|
|
252
|
+
* @param options Connection parameters: base URL and access token.
|
|
253
|
+
*/
|
|
254
|
+
constructor(e) {
|
|
255
|
+
super();
|
|
256
|
+
const t = async (r, n = {}) => {
|
|
257
|
+
const s = await fetch(`${e.baseUrl}${r}`, {
|
|
258
|
+
...n,
|
|
259
|
+
headers: {
|
|
260
|
+
"PRIVATE-TOKEN": e.token,
|
|
261
|
+
...n.headers ?? {}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
if (!s.ok) {
|
|
265
|
+
const o = await s.text().catch(() => "");
|
|
266
|
+
throw new Error(`GitLab request failed: ${s.status} ${s.statusText} ${o}`);
|
|
267
|
+
}
|
|
268
|
+
return s.json();
|
|
269
|
+
};
|
|
270
|
+
this.MergeRequests = new ue(t), this.MergeDiscussions = new fe(t), this.RepositoryFiles = new he(e.baseUrl, e.token);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
class me extends Y {
|
|
274
|
+
/** @purpose Bound HTTP request function for GitHub API calls */
|
|
275
|
+
_request;
|
|
276
|
+
/**
|
|
277
|
+
* @purpose Wire the HTTP request adapter for GitHub PR endpoints.
|
|
278
|
+
* @param request Authenticated HTTP request function targeting GitHub API.
|
|
279
|
+
*/
|
|
280
|
+
constructor(e) {
|
|
281
|
+
super(), this._request = e;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* @purpose GitHub getList not implemented — use getChanges for PR files.
|
|
285
|
+
* @returns Empty array — not implemented.
|
|
286
|
+
*/
|
|
287
|
+
async getList() {
|
|
288
|
+
throw new Error("GitHub getList not implemented — use getChanges for PR files");
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* @purpose GitHub getOne not implemented.
|
|
292
|
+
* @returns Null — not implemented.
|
|
293
|
+
*/
|
|
294
|
+
async getOne() {
|
|
295
|
+
throw new Error("GitHub getOne not implemented");
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* @purpose GitHub getByIid not implemented.
|
|
299
|
+
* @returns Null — not implemented.
|
|
300
|
+
*/
|
|
301
|
+
async getByIid() {
|
|
302
|
+
throw new Error("GitHub getByIid not implemented");
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* @param query Target repository and PR number, optional pagination.
|
|
306
|
+
* @returns List of changed files with metadata.
|
|
307
|
+
* @sideEffect Network: GET /repos/:owner/:repo/pulls/:number/files
|
|
308
|
+
* @see {VcsClientMergeRequests#getChanges} in services/vcs-client/abstract/vcs-client-merge-requests.ts
|
|
309
|
+
*/
|
|
310
|
+
async getChanges(e) {
|
|
311
|
+
const t = new URLSearchParams();
|
|
312
|
+
e?.perPage && t.set("per_page", String(e.perPage)), e?.page && t.set("page", String(e.page));
|
|
313
|
+
const r = encodeURIComponent(e.repository), n = encodeURIComponent(String(e.iid)), [s, o] = await Promise.all([
|
|
314
|
+
this._request(`/repos/${r}/pulls/${n}`),
|
|
315
|
+
this._request(`/repos/${r}/pulls/${n}/files?${t.toString()}`)
|
|
316
|
+
]), a = s?.head?.ref ?? "main";
|
|
317
|
+
return (Array.isArray(o) ? o : []).map((d) => {
|
|
318
|
+
const l = d.filename ?? "", u = d.status ?? "modified", h = d.additions, p = d.deletions;
|
|
319
|
+
let f = "modified";
|
|
320
|
+
u === "added" ? f = "added" : u === "removed" ? f = "deleted" : u === "renamed" && (f = "renamed");
|
|
321
|
+
const m = {
|
|
322
|
+
path: l,
|
|
323
|
+
status: f,
|
|
324
|
+
ref: a
|
|
325
|
+
};
|
|
326
|
+
return f === "renamed" && d.previous_filename && (m.previousPath = d.previous_filename), typeof h == "number" && (m.additions = h), typeof p == "number" && (m.deletions = p), m;
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
class ge extends Q {
|
|
331
|
+
/** @purpose GitHub API base URL */
|
|
332
|
+
_baseUrl;
|
|
333
|
+
/** @purpose GitHub access token */
|
|
334
|
+
_token;
|
|
335
|
+
/**
|
|
336
|
+
* @purpose Wire the adapter with GitHub connection params for raw content fetches.
|
|
337
|
+
* @param baseUrl GitHub API base URL (e.g. https://api.github.com).
|
|
338
|
+
* @param token GitHub personal access token.
|
|
339
|
+
*/
|
|
340
|
+
constructor(e, t) {
|
|
341
|
+
super(), this._baseUrl = e, this._token = t;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* @param query Target repository, file path, and ref.
|
|
345
|
+
* @returns File content or null if file not found.
|
|
346
|
+
* @sideEffect Network: GET /repos/:owner/:repo/contents/:path?ref=
|
|
347
|
+
* @see {VcsClientRepositoryFiles#getFileContent} in services/vcs-client/abstract/vcs-client-repository-files.ts
|
|
348
|
+
*/
|
|
349
|
+
async getFileContent(e) {
|
|
350
|
+
const t = encodeURIComponent(e.repository), r = encodeURIComponent(e.path), n = encodeURIComponent(e.ref);
|
|
351
|
+
let s;
|
|
352
|
+
try {
|
|
353
|
+
s = await fetch(`${this._baseUrl}/repos/${t}/contents/${r}?ref=${n}`, {
|
|
354
|
+
headers: {
|
|
355
|
+
Authorization: "Bearer " + this._token,
|
|
356
|
+
Accept: "application/vnd.github+json",
|
|
357
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
} catch (l) {
|
|
361
|
+
throw new Error(`GitHub request failed: network error for file ${e.path}`, { cause: l });
|
|
362
|
+
}
|
|
363
|
+
if (s.status === 404)
|
|
364
|
+
return null;
|
|
365
|
+
if (!s.ok) {
|
|
366
|
+
const l = await s.text().catch(() => "");
|
|
367
|
+
throw new Error(`GitHub request failed: ${s.status} ${s.statusText} ${l}`);
|
|
368
|
+
}
|
|
369
|
+
const o = await s.json(), a = o.encoding ?? "none", c = o.content ?? "";
|
|
370
|
+
if (a === "base64") {
|
|
371
|
+
const l = Buffer.from(c, "base64").toString("utf-8"), u = !l.includes("\0");
|
|
372
|
+
return {
|
|
373
|
+
path: e.path,
|
|
374
|
+
content: u ? l : c,
|
|
375
|
+
encoding: u ? "utf-8" : "base64"
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
const d = o.type;
|
|
379
|
+
return d === "submodule" || d === "symlink" ? {
|
|
380
|
+
path: e.path,
|
|
381
|
+
content: o.submodule_git_url ?? o.target ?? "",
|
|
382
|
+
encoding: "utf-8"
|
|
383
|
+
} : {
|
|
384
|
+
path: e.path,
|
|
385
|
+
content: c,
|
|
386
|
+
encoding: "utf-8"
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
class yt extends Z {
|
|
391
|
+
/** @see {VcsClient#MergeRequests} in services/vcs-client/abstract/vcs-client.ts */
|
|
392
|
+
MergeRequests;
|
|
393
|
+
/** @see {VcsClient#MergeDiscussions} in services/vcs-client/abstract/vcs-client.ts */
|
|
394
|
+
MergeDiscussions = void 0;
|
|
395
|
+
/** @see {VcsClient#RepositoryFiles} in services/vcs-client/abstract/vcs-client.ts */
|
|
396
|
+
RepositoryFiles;
|
|
397
|
+
/**
|
|
398
|
+
* @purpose Create a GitHub API client bound to a base URL with access token.
|
|
399
|
+
* @param options Connection parameters: base URL and access token.
|
|
400
|
+
*/
|
|
401
|
+
constructor(e) {
|
|
402
|
+
super();
|
|
403
|
+
const t = async (r, n = {}) => {
|
|
404
|
+
const s = await fetch(`${e.baseUrl}${r}`, {
|
|
405
|
+
...n,
|
|
406
|
+
headers: {
|
|
407
|
+
Authorization: "Bearer " + e.token,
|
|
408
|
+
Accept: "application/vnd.github+json",
|
|
409
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
410
|
+
...n.headers ?? {}
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
if (!s.ok) {
|
|
414
|
+
const o = await s.text().catch(() => "");
|
|
415
|
+
throw new Error(`GitHub request failed: ${s.status} ${s.statusText} ${o}`);
|
|
416
|
+
}
|
|
417
|
+
return s.json();
|
|
418
|
+
};
|
|
419
|
+
this.MergeRequests = new me(t), this.RepositoryFiles = new ge(e.baseUrl, e.token);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function L(i) {
|
|
423
|
+
try {
|
|
424
|
+
if (i === null)
|
|
425
|
+
return { kind: "primitive", text: "null" };
|
|
426
|
+
if (i === void 0)
|
|
427
|
+
return { kind: "primitive", text: "undefined" };
|
|
428
|
+
const e = typeof i;
|
|
429
|
+
if (e === "string" || e === "number" || e === "boolean" || e === "bigint" || e === "symbol")
|
|
430
|
+
return { kind: "primitive", text: String(i) };
|
|
431
|
+
if (e === "function")
|
|
432
|
+
return {
|
|
433
|
+
kind: "tagged",
|
|
434
|
+
tag: "[object Function]",
|
|
435
|
+
text: `[function ${i.name || "anonymous"}]`
|
|
436
|
+
};
|
|
437
|
+
const t = Object.prototype.toString.call(i);
|
|
438
|
+
if (i instanceof Error) {
|
|
439
|
+
const n = i.message ? `${i.name}: ${i.message}` : i.name;
|
|
440
|
+
return { kind: "tagged", tag: t, text: n };
|
|
441
|
+
}
|
|
442
|
+
const r = _e(i);
|
|
443
|
+
return r ? { kind: "tagged", tag: t, text: `${t} ${r}` } : { kind: "tagged", tag: t, text: t };
|
|
444
|
+
} catch (e) {
|
|
445
|
+
return {
|
|
446
|
+
kind: "tagged",
|
|
447
|
+
tag: "[object Unserializable]",
|
|
448
|
+
text: `[unserializable: ${String(e)}]`
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function _e(i) {
|
|
453
|
+
const e = /* @__PURE__ */ new WeakSet();
|
|
454
|
+
try {
|
|
455
|
+
return JSON.stringify(i, (r, n) => {
|
|
456
|
+
if (typeof n == "bigint")
|
|
457
|
+
return `${n.toString()}n`;
|
|
458
|
+
if (typeof n == "symbol")
|
|
459
|
+
return n.toString();
|
|
460
|
+
if (typeof n == "function")
|
|
461
|
+
return `[function ${n.name || "anonymous"}]`;
|
|
462
|
+
if (n && typeof n == "object") {
|
|
463
|
+
if (e.has(n))
|
|
464
|
+
return "[circular]";
|
|
465
|
+
e.add(n);
|
|
466
|
+
}
|
|
467
|
+
return n;
|
|
468
|
+
}) ?? null;
|
|
469
|
+
} catch {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const O = ["log", "info", "warn", "error", "debug"], ye = 5e3, P = /* @__PURE__ */ new WeakMap(), C = globalThis;
|
|
474
|
+
function be(i, e) {
|
|
475
|
+
we(i, e);
|
|
476
|
+
const t = P.get(i);
|
|
477
|
+
if (t)
|
|
478
|
+
return t.duplicateConnectWarned || (t.duplicateConnectWarned = !0, t.originals.warn(
|
|
479
|
+
"[remoteConsoleClient.connect] [connected → connected] Duplicate connect ignored for existing target"
|
|
480
|
+
)), t.runtime;
|
|
481
|
+
const r = ve(i), s = {
|
|
482
|
+
disconnect: async () => {
|
|
483
|
+
const a = P.get(i);
|
|
484
|
+
a && await k(a, !1);
|
|
485
|
+
}
|
|
486
|
+
}, o = {
|
|
487
|
+
url: e.url,
|
|
488
|
+
tabId: e.tabId,
|
|
489
|
+
branch: e.branch,
|
|
490
|
+
consoleTarget: i,
|
|
491
|
+
originals: r,
|
|
492
|
+
queue: [],
|
|
493
|
+
flushInFlight: !1,
|
|
494
|
+
transportFailureNotified: !1,
|
|
495
|
+
duplicateConnectWarned: !1,
|
|
496
|
+
shutdownStarted: !1,
|
|
497
|
+
intervalId: null,
|
|
498
|
+
pagehideListener: () => {
|
|
499
|
+
k(o, !0);
|
|
500
|
+
},
|
|
501
|
+
beforeunloadListener: () => {
|
|
502
|
+
k(o, !0);
|
|
503
|
+
},
|
|
504
|
+
runtime: s
|
|
505
|
+
};
|
|
506
|
+
return Ie(o), Se(o), xe(o), o.intervalId = setInterval(() => {
|
|
507
|
+
ee(o, !1);
|
|
508
|
+
}, ye), i.__remote__ = s, P.set(i, o), s;
|
|
509
|
+
}
|
|
510
|
+
const bt = {
|
|
511
|
+
connect: be
|
|
512
|
+
};
|
|
513
|
+
function we(i, e) {
|
|
514
|
+
try {
|
|
515
|
+
new URL(e.url);
|
|
516
|
+
} catch (t) {
|
|
517
|
+
throw new Error("[remoteConsoleClient.connect] Invalid config.url: expected absolute URL", {
|
|
518
|
+
cause: t
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
for (const t of O)
|
|
522
|
+
if (typeof i[t] != "function")
|
|
523
|
+
throw new Error(`[remoteConsoleClient.connect] Missing console method: ${t}`);
|
|
524
|
+
}
|
|
525
|
+
function ve(i) {
|
|
526
|
+
return {
|
|
527
|
+
log: i.log.bind(i),
|
|
528
|
+
info: i.info.bind(i),
|
|
529
|
+
warn: i.warn.bind(i),
|
|
530
|
+
error: i.error.bind(i),
|
|
531
|
+
debug: i.debug.bind(i)
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function Ie(i) {
|
|
535
|
+
for (const e of O) {
|
|
536
|
+
const t = i.originals[e];
|
|
537
|
+
i.consoleTarget[e] = (...r) => {
|
|
538
|
+
t(...r), !i.shutdownStarted && i.queue.push({
|
|
539
|
+
level: e,
|
|
540
|
+
timestamp: Date.now(),
|
|
541
|
+
args: r.map((n) => L(n)),
|
|
542
|
+
tabId: i.tabId,
|
|
543
|
+
branch: i.branch
|
|
544
|
+
});
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function Se(i) {
|
|
549
|
+
i.queue.push({
|
|
550
|
+
level: "info",
|
|
551
|
+
timestamp: Date.now(),
|
|
552
|
+
args: [
|
|
553
|
+
L("[remote-console] connected"),
|
|
554
|
+
L(`tabId=${i.tabId ?? "unknown"}`),
|
|
555
|
+
L(`branch=${i.branch ?? "unknown"}`)
|
|
556
|
+
],
|
|
557
|
+
tabId: i.tabId,
|
|
558
|
+
branch: i.branch
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
function Ce(i) {
|
|
562
|
+
for (const e of O)
|
|
563
|
+
i.consoleTarget[e] = i.originals[e];
|
|
564
|
+
delete i.consoleTarget.__remote__;
|
|
565
|
+
}
|
|
566
|
+
function xe(i) {
|
|
567
|
+
typeof C.addEventListener == "function" && (C.addEventListener("pagehide", i.pagehideListener), C.addEventListener("beforeunload", i.beforeunloadListener));
|
|
568
|
+
}
|
|
569
|
+
function Re(i) {
|
|
570
|
+
typeof C.removeEventListener == "function" && (C.removeEventListener("pagehide", i.pagehideListener), C.removeEventListener("beforeunload", i.beforeunloadListener));
|
|
571
|
+
}
|
|
572
|
+
async function k(i, e) {
|
|
573
|
+
if (!i.shutdownStarted) {
|
|
574
|
+
i.shutdownStarted = !0, i.intervalId && (clearInterval(i.intervalId), i.intervalId = null), Re(i);
|
|
575
|
+
try {
|
|
576
|
+
await ee(i, e);
|
|
577
|
+
try {
|
|
578
|
+
await te(i, { type: "disconnect", tabId: i.tabId }, e);
|
|
579
|
+
} catch {
|
|
580
|
+
}
|
|
581
|
+
} finally {
|
|
582
|
+
Ce(i), P.delete(i.consoleTarget);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
async function ee(i, e) {
|
|
587
|
+
if (i.flushInFlight || i.queue.length === 0)
|
|
588
|
+
return;
|
|
589
|
+
i.flushInFlight = !0;
|
|
590
|
+
const t = i.queue.splice(0, i.queue.length);
|
|
591
|
+
try {
|
|
592
|
+
await te(i, { type: "logs", tabId: i.tabId, items: t }, e);
|
|
593
|
+
} catch (r) {
|
|
594
|
+
i.transportFailureNotified || (i.transportFailureNotified = !0, i.originals.error(
|
|
595
|
+
"[remoteConsoleClient.connect] [streaming → degraded] Remote sink became unavailable; local console remains active",
|
|
596
|
+
{ cause: r }
|
|
597
|
+
));
|
|
598
|
+
} finally {
|
|
599
|
+
i.flushInFlight = !1;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
async function te(i, e, t) {
|
|
603
|
+
if (t) {
|
|
604
|
+
await Ee(i.url, e);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const r = await fetch(i.url, {
|
|
608
|
+
method: "POST",
|
|
609
|
+
headers: {
|
|
610
|
+
"content-type": "application/json"
|
|
611
|
+
},
|
|
612
|
+
body: JSON.stringify(e)
|
|
613
|
+
});
|
|
614
|
+
if (!r.ok)
|
|
615
|
+
throw new Error(`[remoteConsoleClient.connect] Unexpected response status: ${r.status}`);
|
|
616
|
+
}
|
|
617
|
+
async function Ee(i, e) {
|
|
618
|
+
const t = JSON.stringify(e), r = new Blob([t], { type: "application/json" }), n = C.navigator?.sendBeacon;
|
|
619
|
+
if (!(typeof n == "function" && n.call(C.navigator, i, r)))
|
|
620
|
+
try {
|
|
621
|
+
await fetch(i, {
|
|
622
|
+
method: "POST",
|
|
623
|
+
headers: {
|
|
624
|
+
"content-type": "application/json"
|
|
625
|
+
},
|
|
626
|
+
body: t,
|
|
627
|
+
keepalive: !0
|
|
628
|
+
});
|
|
629
|
+
} catch {
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
class $e {
|
|
633
|
+
/** @purpose Stores low-level line sink used to emit already-rendered stdout entries. */
|
|
634
|
+
_writeLine;
|
|
635
|
+
/**
|
|
636
|
+
* @purpose Configures writer with injected sink or defaults to process.stdout line output.
|
|
637
|
+
* @param [writeLine] Optional custom line writer function.
|
|
638
|
+
*/
|
|
639
|
+
constructor(e) {
|
|
640
|
+
this._writeLine = e ?? ((t) => process.stdout.write(`${t}
|
|
641
|
+
`));
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* @purpose Prints ordered log entries as one normalized line per entry.
|
|
645
|
+
* @param items Parsed log entry list from command envelope payload.
|
|
646
|
+
* @sideEffect IO: writes rendered lines into stdout stream (or injected writer).
|
|
647
|
+
*/
|
|
648
|
+
write(e) {
|
|
649
|
+
for (const t of e) {
|
|
650
|
+
const r = this.render(t);
|
|
651
|
+
this._writeLine(r);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* @purpose Creates stable text format independent from platform util.inspect behavior.
|
|
656
|
+
* @param item Single remote log entry with serialized args.
|
|
657
|
+
* @returns Flat line in format `[console.<level>] <arg1> <arg2> ...`.
|
|
658
|
+
*/
|
|
659
|
+
render(e) {
|
|
660
|
+
const t = e.args.map((r) => r.text).join(" ").trim();
|
|
661
|
+
return t ? `[console.${e.level}] ${t}` : `[console.${e.level}]`;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const ne = "/";
|
|
665
|
+
async function wt(i) {
|
|
666
|
+
Ae(i);
|
|
667
|
+
const e = i.host ?? "127.0.0.1", t = new $e(i.stdoutWrite), r = new Le(
|
|
668
|
+
i.exitCode ?? 0,
|
|
669
|
+
i.exit
|
|
670
|
+
), n = ie((o, a) => {
|
|
671
|
+
Te(o, a, t, r, n).catch(
|
|
672
|
+
() => {
|
|
673
|
+
a.writableEnded || v(a, 500, { ok: !1, error: "runtime_failure" });
|
|
674
|
+
}
|
|
675
|
+
);
|
|
676
|
+
});
|
|
677
|
+
await new Promise((o, a) => {
|
|
678
|
+
n.once("error", a), n.listen(i.port, e, () => {
|
|
679
|
+
n.off("error", a), o();
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
const s = n.address();
|
|
683
|
+
if (!s || typeof s == "string")
|
|
684
|
+
throw new Error("[startRemoteConsoleServer] Failed to resolve runtime endpoint address");
|
|
685
|
+
return {
|
|
686
|
+
url: `http://${e}:${s.port}${ne}`,
|
|
687
|
+
close: async () => {
|
|
688
|
+
await re(n);
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
function Ae(i) {
|
|
693
|
+
if (!Number.isInteger(i.port) || i.port < 0 || i.port > 65535)
|
|
694
|
+
throw new Error("[startRemoteConsoleServer] Invalid port: expected integer in range 0..65535");
|
|
695
|
+
}
|
|
696
|
+
async function Te(i, e, t, r, n) {
|
|
697
|
+
try {
|
|
698
|
+
if (i.url !== ne) {
|
|
699
|
+
v(e, 404, { ok: !1, error: "not_found" });
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (i.method !== "POST") {
|
|
703
|
+
v(e, 405, { ok: !1, error: "method_not_allowed" });
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const s = await Me(i);
|
|
707
|
+
let o;
|
|
708
|
+
try {
|
|
709
|
+
o = JSON.parse(s);
|
|
710
|
+
} catch {
|
|
711
|
+
v(e, 400, { ok: !1, error: "invalid_json" });
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
if (!Ne(o)) {
|
|
715
|
+
v(e, 400, { ok: !1, error: "invalid_envelope" });
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (o.type === "logs") {
|
|
719
|
+
if (!Array.isArray(o.items) || o.items.length === 0) {
|
|
720
|
+
v(e, 400, { ok: !1, error: "invalid_logs_payload" });
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
t.write(o.items), v(e, 202, { ok: !0 });
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (o.type === "disconnect") {
|
|
727
|
+
v(e, 202, { ok: !0 }), await r.begin(n);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
v(e, 400, { ok: !1, error: "unsupported_command" });
|
|
731
|
+
} catch {
|
|
732
|
+
e.writableEnded || v(e, 500, { ok: !1, error: "runtime_failure" });
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
function Ne(i) {
|
|
736
|
+
if (!i || typeof i != "object")
|
|
737
|
+
return !1;
|
|
738
|
+
const e = i.type;
|
|
739
|
+
return e === "disconnect" ? !0 : e === "logs" ? Array.isArray(i.items) : !1;
|
|
740
|
+
}
|
|
741
|
+
async function Me(i) {
|
|
742
|
+
let e = "";
|
|
743
|
+
for await (const t of i)
|
|
744
|
+
e += t.toString();
|
|
745
|
+
return e;
|
|
746
|
+
}
|
|
747
|
+
function v(i, e, t) {
|
|
748
|
+
i.statusCode = e, i.setHeader("content-type", "application/json"), i.end(JSON.stringify(t));
|
|
749
|
+
}
|
|
750
|
+
async function re(i) {
|
|
751
|
+
await new Promise((e, t) => {
|
|
752
|
+
i.close((r) => {
|
|
753
|
+
if (r) {
|
|
754
|
+
t(r);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
e();
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
class Le {
|
|
762
|
+
/** @purpose Stores configured exit code used after controlled socket shutdown completes. */
|
|
763
|
+
_exitCode;
|
|
764
|
+
/** @purpose Stores process-exit strategy (real exit in prod, spy callback in tests). */
|
|
765
|
+
_exit;
|
|
766
|
+
/** @purpose Prevents duplicate shutdown orchestration across repeated disconnect signals. */
|
|
767
|
+
_started = !1;
|
|
768
|
+
/** @purpose Initializes controller with deterministic exit behavior for runtime or test harnesses. */
|
|
769
|
+
constructor(e, t) {
|
|
770
|
+
this._exitCode = e, this._exit = t ?? ((r) => process.exit(r));
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* @purpose Starts shutdown exactly once: close server socket first, then trigger configured exit strategy.
|
|
774
|
+
* @param server Active server runtime instance.
|
|
775
|
+
* @sideEffect Network: stops accepting incoming HTTP connections.
|
|
776
|
+
* @sideEffect Process: triggers exit callback with configured exit code.
|
|
777
|
+
*/
|
|
778
|
+
async begin(e) {
|
|
779
|
+
if (!this._started) {
|
|
780
|
+
this._started = !0;
|
|
781
|
+
try {
|
|
782
|
+
await re(e);
|
|
783
|
+
} finally {
|
|
784
|
+
this._exit(this._exitCode);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
const j = "ERR_DBC_PURPOSE_CONFLICT", Pe = "ERR_DBC_ORDER", ke = "ERR_DBC_PARAM_NAME_MISSING", De = "ERR_DBC_SEE_FORMAT_INVALID", Oe = [
|
|
790
|
+
"implements",
|
|
791
|
+
"invariant",
|
|
792
|
+
"pre",
|
|
793
|
+
"param",
|
|
794
|
+
"throws",
|
|
795
|
+
"returns",
|
|
796
|
+
"post",
|
|
797
|
+
"sideEffect"
|
|
798
|
+
], Fe = new Map(
|
|
799
|
+
Oe.map((i, e) => [i, e])
|
|
800
|
+
);
|
|
801
|
+
class vt {
|
|
802
|
+
/**
|
|
803
|
+
* @param inputContract Raw contract text to parse.
|
|
804
|
+
* @returns Parsed DBC schema with entries and format metadata.
|
|
805
|
+
* @see {DbcParser#parse} in ../../dbc-parser.types.ts
|
|
806
|
+
*/
|
|
807
|
+
parse(e) {
|
|
808
|
+
const t = [];
|
|
809
|
+
let n = e.trim();
|
|
810
|
+
n.startsWith("/**") && (n = n.slice(3)), n.endsWith("*/") && (n = n.slice(0, -2)), n = n.trim();
|
|
811
|
+
const s = n.includes(`
|
|
812
|
+
`) ? "multi-line" : "single-line", o = e.split(/\r?\n/u);
|
|
813
|
+
let a, c = !1;
|
|
814
|
+
const d = [];
|
|
815
|
+
for (let u = 0; u < o.length; u += 1) {
|
|
816
|
+
const h = u + 1, p = this.normalizeLine(o[u] ?? "");
|
|
817
|
+
if (p.length !== 0) {
|
|
818
|
+
if (p.startsWith("@")) {
|
|
819
|
+
!c && d.length > 0 && (t.push({
|
|
820
|
+
type: "description",
|
|
821
|
+
value: d.join(`
|
|
822
|
+
`),
|
|
823
|
+
issues: [],
|
|
824
|
+
line: 1
|
|
825
|
+
}), d.length = 0), c = !0;
|
|
826
|
+
const f = this.parseTagLine(p, h);
|
|
827
|
+
if (!f)
|
|
828
|
+
continue;
|
|
829
|
+
t.push(f), a = f;
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
if (!c) {
|
|
833
|
+
d.push(p);
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
a && (a.value = a.value.length > 0 ? `${a.value}
|
|
837
|
+
${p}` : p);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
!c && d.length > 0 && t.push({
|
|
841
|
+
type: "description",
|
|
842
|
+
value: d.join(`
|
|
843
|
+
`),
|
|
844
|
+
issues: [],
|
|
845
|
+
line: 1
|
|
846
|
+
}), this.validatePurposeConflict(t), this.validateContractOrder(t), this.validateParamSpecifier(t), this.validateSeeSpecifier(t);
|
|
847
|
+
let l;
|
|
848
|
+
if (s === "single-line" && t.length > 0) {
|
|
849
|
+
const u = t[0];
|
|
850
|
+
if (u && u.value.includes(" | @")) {
|
|
851
|
+
const h = u.value.split(" | @");
|
|
852
|
+
u.value = (h[0] ?? "").trim();
|
|
853
|
+
const p = [];
|
|
854
|
+
for (let f = 1; f < h.length; f += 1) {
|
|
855
|
+
const m = `@${h[f] ?? ""}`, _ = this.parseTagLine(m, u.line);
|
|
856
|
+
_ && p.push(_);
|
|
857
|
+
}
|
|
858
|
+
p.length > 0 && (l = p);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return {
|
|
862
|
+
entries: t.map(({ line: u, ...h }, p) => p === 0 && l ? {
|
|
863
|
+
...h,
|
|
864
|
+
inline: l.map(({ line: f, ...m }) => m)
|
|
865
|
+
} : h),
|
|
866
|
+
format: s
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* @purpose Normalizes a raw source line by stripping JSDoc framing tokens and edge whitespace.
|
|
871
|
+
* @param sourceLine Raw line from the input contract.
|
|
872
|
+
* @returns Line prepared for tag and multiline-value parsing.
|
|
873
|
+
*/
|
|
874
|
+
normalizeLine(e) {
|
|
875
|
+
let t = e.trimStart();
|
|
876
|
+
return t.startsWith("*/") ? "" : (t.startsWith("/**") && (t = t.slice(3)), t = t.trimStart(), t.startsWith("*") && (t = t.slice(1).trimStart()), t.endsWith("*/") && (t = t.slice(0, -2).trimEnd()), t.trim());
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* @purpose Parses a normalized tag line into an entry object with a source line reference.
|
|
880
|
+
* @param tagLine Normalized line starting with `@`.
|
|
881
|
+
* @param line One-based source line number.
|
|
882
|
+
* @returns Parsed entry or undefined when the tag name is missing.
|
|
883
|
+
*/
|
|
884
|
+
parseTagLine(e, t) {
|
|
885
|
+
const r = /^@(\S+)(?:\s+([\s\S]*))?$/u.exec(e);
|
|
886
|
+
if (!r)
|
|
887
|
+
return;
|
|
888
|
+
const n = r[1]?.trim();
|
|
889
|
+
if (!n)
|
|
890
|
+
return;
|
|
891
|
+
const s = (r[2] ?? "").trim();
|
|
892
|
+
return n === "param" ? this.parseParamTag(n, s, t) : n === "see" ? this.parseSeeTag(n, s, t) : n === "implements" ? this.parseImplementsTag(n, s, t) : n === "returns" || n === "throws" ? this.parseTagWithOptionalDataType(n, s, t) : n === "description" ? {
|
|
893
|
+
type: "description",
|
|
894
|
+
value: s,
|
|
895
|
+
issues: [],
|
|
896
|
+
line: t
|
|
897
|
+
} : {
|
|
898
|
+
type: n,
|
|
899
|
+
value: s,
|
|
900
|
+
issues: [],
|
|
901
|
+
line: t
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* @purpose Parses tags where `{dataType}` is optional and the remainder is a plain value.
|
|
906
|
+
* @param type Tag type.
|
|
907
|
+
* @param tail Content after the tag name.
|
|
908
|
+
* @param line One-based source line number.
|
|
909
|
+
* @returns Parsed entry.
|
|
910
|
+
*/
|
|
911
|
+
parseTagWithOptionalDataType(e, t, r) {
|
|
912
|
+
const n = /^\{([^}]+)\}\s*(.*)$/u.exec(t);
|
|
913
|
+
return n ? {
|
|
914
|
+
type: e,
|
|
915
|
+
dataType: n[1]?.trim(),
|
|
916
|
+
value: (n[2] ?? "").trim(),
|
|
917
|
+
issues: [],
|
|
918
|
+
line: r
|
|
919
|
+
} : {
|
|
920
|
+
type: e,
|
|
921
|
+
value: t,
|
|
922
|
+
issues: [],
|
|
923
|
+
line: r
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* @purpose Parses `@param` into `dataType`, optional `specifier`, `optional`, and value fields.
|
|
928
|
+
* @param type Tag type.
|
|
929
|
+
* @param tail Content after `@param`.
|
|
930
|
+
* @param line One-based source line number.
|
|
931
|
+
* @returns Parsed entry.
|
|
932
|
+
*/
|
|
933
|
+
parseParamTag(e, t, r) {
|
|
934
|
+
const n = {
|
|
935
|
+
type: e,
|
|
936
|
+
value: "",
|
|
937
|
+
issues: [],
|
|
938
|
+
line: r
|
|
939
|
+
};
|
|
940
|
+
let s = t;
|
|
941
|
+
const o = /^\{([^}]+)\}\s*(.*)$/u.exec(s);
|
|
942
|
+
if (o && (n.dataType = o[1]?.trim(), s = (o[2] ?? "").trim()), s.length === 0)
|
|
943
|
+
return n;
|
|
944
|
+
const a = /^(\[[^\]]+\]|\S+)(?:\s+([\s\S]*))?$/u.exec(s);
|
|
945
|
+
if (!a)
|
|
946
|
+
return n.value = s, n;
|
|
947
|
+
const c = a[1] ?? "";
|
|
948
|
+
return c.startsWith("[") && c.endsWith("]") ? (n.optional = !0, n.specifier = c.slice(1, -1).trim()) : n.specifier = c.trim(), n.value = (a[2] ?? "").trim(), n;
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* @purpose Parses `@see` into required `{specifier}` and optional trailing value.
|
|
952
|
+
* @param type Tag type.
|
|
953
|
+
* @param tail Content after `@see`.
|
|
954
|
+
* @param line Source line number.
|
|
955
|
+
* @returns Parsed entry.
|
|
956
|
+
*/
|
|
957
|
+
parseSeeTag(e, t, r) {
|
|
958
|
+
const n = {
|
|
959
|
+
type: e,
|
|
960
|
+
value: "",
|
|
961
|
+
issues: [],
|
|
962
|
+
line: r
|
|
963
|
+
}, s = /^\{([^}]+)\}\s*(.*)$/u.exec(t);
|
|
964
|
+
return s ? (n.specifier = s[1]?.trim(), n.value = (s[2] ?? "").trim(), n) : (n.value = t, n);
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* @purpose Parses `@implements` into required `{ContractName}` and optional trailing value.
|
|
968
|
+
* @param type Tag type.
|
|
969
|
+
* @param tail Content after `@implements`.
|
|
970
|
+
* @param line One-based source line number.
|
|
971
|
+
* @returns Parsed entry.
|
|
972
|
+
*/
|
|
973
|
+
parseImplementsTag(e, t, r) {
|
|
974
|
+
const n = {
|
|
975
|
+
type: e,
|
|
976
|
+
value: "",
|
|
977
|
+
issues: [],
|
|
978
|
+
line: r
|
|
979
|
+
}, s = /^\{([^}]+)\}\s*(.*)$/u.exec(t);
|
|
980
|
+
return s ? (n.specifier = s[1]?.trim(), n.value = (s[2] ?? "").trim(), n) : (n.value = t, n);
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* @purpose Emits a conflict issue when `purpose` and `see` are both present in the parsed entries.
|
|
984
|
+
* @param entries Parsed entries with source lines.
|
|
985
|
+
*/
|
|
986
|
+
validatePurposeConflict(e) {
|
|
987
|
+
let t = !1, r = !1;
|
|
988
|
+
for (const n of e) {
|
|
989
|
+
if (n.type === "purpose") {
|
|
990
|
+
r && this.attachIssue(n, j), t = !0;
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
n.type === "see" && (t && this.attachIssue(n, j), r = !0);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* @purpose Verifies that contract tags follow the strict sequence required by the DBC standard.
|
|
998
|
+
* @param entries Parsed entries with source lines.
|
|
999
|
+
*/
|
|
1000
|
+
validateContractOrder(e) {
|
|
1001
|
+
let t = -1;
|
|
1002
|
+
for (const r of e) {
|
|
1003
|
+
const n = Fe.get(r.type);
|
|
1004
|
+
n !== void 0 && (n < t && this.attachIssue(r, Pe), t = Math.max(t, n));
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* @purpose Emits missing-parameter-name issues for `@param` entries without a non-empty specifier.
|
|
1009
|
+
* @param entries Parsed entries with source lines.
|
|
1010
|
+
*/
|
|
1011
|
+
validateParamSpecifier(e) {
|
|
1012
|
+
for (const t of e)
|
|
1013
|
+
t.type === "param" && (!t.specifier || t.specifier.trim().length === 0) && this.attachIssue(t, ke);
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* @purpose Emits invalid-see-format issues for `@see` entries.
|
|
1017
|
+
* @param entries Parsed entries to validate.
|
|
1018
|
+
*/
|
|
1019
|
+
validateSeeSpecifier(e) {
|
|
1020
|
+
for (const t of e)
|
|
1021
|
+
t.type === "see" && (!t.specifier || t.specifier.trim().length === 0) && this.attachIssue(t, De);
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* @purpose Adds an issue to a specific entry and preserves the entry start line in diagnostics.
|
|
1025
|
+
* @param entry Parsed entry with source line.
|
|
1026
|
+
* @param code Stable parser issue code.
|
|
1027
|
+
*/
|
|
1028
|
+
attachIssue(e, t) {
|
|
1029
|
+
e.issues.push({
|
|
1030
|
+
code: t,
|
|
1031
|
+
line: e.line
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
const W = "ERR_DBC_LINT_MISSING_CONTRACT", N = "ERR_DBC_LINT_PARSE_FAILED", Ue = "ERR_DBC_LINT_PARAM_MISSING", J = "ERR_DBC_LINT_PARAM_EXTRA", Be = "ERR_DBC_LINT_PARAM_ORDER", G = "ERR_DBC_LINT_RETURNS_MISSING", z = "ERR_DBC_LINT_RETURNS_UNEXPECTED", je = "ERR_DBC_LINT_TYPE_REDUNDANT", H = "ERR_DBC_LINT_PARAM_OPTIONAL_MISMATCH", F = /* @__PURE__ */ new Set([
|
|
1036
|
+
"function",
|
|
1037
|
+
"method",
|
|
1038
|
+
"interface-method",
|
|
1039
|
+
"constructor",
|
|
1040
|
+
"export-default"
|
|
1041
|
+
]), U = /* @__PURE__ */ new Set([
|
|
1042
|
+
"function",
|
|
1043
|
+
"method",
|
|
1044
|
+
"interface-method",
|
|
1045
|
+
"export-default"
|
|
1046
|
+
]), se = /* @__PURE__ */ new Set(["getter"]), B = /* @__PURE__ */ new Set([
|
|
1047
|
+
"constructor",
|
|
1048
|
+
"setter",
|
|
1049
|
+
"field",
|
|
1050
|
+
"interface-property",
|
|
1051
|
+
"const",
|
|
1052
|
+
"type",
|
|
1053
|
+
"enum",
|
|
1054
|
+
"enum-member",
|
|
1055
|
+
"class",
|
|
1056
|
+
"interface"
|
|
1057
|
+
]), We = /* @__PURE__ */ new Set([
|
|
1058
|
+
...F,
|
|
1059
|
+
...U,
|
|
1060
|
+
...se,
|
|
1061
|
+
...B,
|
|
1062
|
+
"export-default"
|
|
1063
|
+
]);
|
|
1064
|
+
function x(i) {
|
|
1065
|
+
let e = i;
|
|
1066
|
+
return e.startsWith("...") && (e = e.slice(3)), e.startsWith("[") && e.endsWith("]") && (e = e.slice(1, -1)), e;
|
|
1067
|
+
}
|
|
1068
|
+
function y(i, e) {
|
|
1069
|
+
return {
|
|
1070
|
+
file: "",
|
|
1071
|
+
line: 1,
|
|
1072
|
+
col: 1,
|
|
1073
|
+
severity: "error",
|
|
1074
|
+
code: i,
|
|
1075
|
+
message: e
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
function Je(i, e, t) {
|
|
1079
|
+
if (!We.has(t))
|
|
1080
|
+
return [];
|
|
1081
|
+
const r = [], n = i.filter((l) => l.type === "param"), s = i.find((l) => l.type === "returns"), o = F.has(t), a = U.has(t), c = se.has(t), d = B.has(t);
|
|
1082
|
+
if (o) {
|
|
1083
|
+
const l = e.params.map((p) => p.name), u = n.map((p) => x(p.specifier ?? ""));
|
|
1084
|
+
for (const p of l)
|
|
1085
|
+
u.includes(p) || r.push(
|
|
1086
|
+
y(Ue, `Parameter '${p}' is missing from contract`)
|
|
1087
|
+
);
|
|
1088
|
+
for (let p = 0; p < n.length; p += 1) {
|
|
1089
|
+
const f = u[p];
|
|
1090
|
+
l.includes(f) || r.push(
|
|
1091
|
+
y(
|
|
1092
|
+
J,
|
|
1093
|
+
`Extra @param '${n[p].specifier ?? ""}' not in signature`
|
|
1094
|
+
)
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
const h = new Map(e.params.map((p) => [p.name, p]));
|
|
1098
|
+
for (const p of n) {
|
|
1099
|
+
const f = x(p.specifier ?? ""), m = h.get(f);
|
|
1100
|
+
m && !m.isRest && (m.optional && !p.optional ? r.push(
|
|
1101
|
+
y(
|
|
1102
|
+
H,
|
|
1103
|
+
`Parameter '${f}' is optional in signature but missing brackets in contract`
|
|
1104
|
+
)
|
|
1105
|
+
) : !m.optional && p.optional && r.push(
|
|
1106
|
+
y(
|
|
1107
|
+
H,
|
|
1108
|
+
`Parameter '${f}' is not optional in signature but has brackets in contract`
|
|
1109
|
+
)
|
|
1110
|
+
));
|
|
1111
|
+
}
|
|
1112
|
+
if (l.length === u.length && l.every((p) => u.includes(p))) {
|
|
1113
|
+
let p = 0, f = !1;
|
|
1114
|
+
for (const m of u) {
|
|
1115
|
+
const _ = l.indexOf(m);
|
|
1116
|
+
if (_ >= 0) {
|
|
1117
|
+
if (_ < p) {
|
|
1118
|
+
f = !0;
|
|
1119
|
+
break;
|
|
1120
|
+
}
|
|
1121
|
+
p = _;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
f && r.push(y(Be, "@param order does not match signature"));
|
|
1125
|
+
}
|
|
1126
|
+
} else
|
|
1127
|
+
for (let l = 0; l < n.length; l += 1)
|
|
1128
|
+
r.push(y(J, `@param not applicable for kind '${t}'`));
|
|
1129
|
+
c ? s || r.push(y(G, `Missing @returns for kind '${t}'`)) : a ? (e.returnType !== "void" && !s && r.push(
|
|
1130
|
+
y(
|
|
1131
|
+
G,
|
|
1132
|
+
`Missing @returns for non-void return type '${e.returnType}'`
|
|
1133
|
+
)
|
|
1134
|
+
), e.returnType === "void" && s && r.push(
|
|
1135
|
+
y(z, "Unexpected @returns for void return type")
|
|
1136
|
+
)) : d && s && r.push(
|
|
1137
|
+
y(z, `@returns not applicable for kind '${t}'`)
|
|
1138
|
+
);
|
|
1139
|
+
for (const l of i)
|
|
1140
|
+
(l.type === "param" || l.type === "returns") && l.dataType && r.push(
|
|
1141
|
+
y(je, `Redundant {${l.dataType}} in @${l.type}`)
|
|
1142
|
+
);
|
|
1143
|
+
return r;
|
|
1144
|
+
}
|
|
1145
|
+
function R(i) {
|
|
1146
|
+
return i.map((e) => `${e.file}:${e.line}:${e.col}: ${e.severity}: ${e.code}: ${e.message}`).join(`
|
|
1147
|
+
`);
|
|
1148
|
+
}
|
|
1149
|
+
class It {
|
|
1150
|
+
/** @purpose DBC parser for parsing contract text */
|
|
1151
|
+
_parser;
|
|
1152
|
+
/** @purpose AST adapter for parsing TypeScript source files */
|
|
1153
|
+
_astAdapter;
|
|
1154
|
+
/**
|
|
1155
|
+
* @purpose Construct a linter with injected parser and AST adapter.
|
|
1156
|
+
* @param parser DbcParser instance for contract parsing.
|
|
1157
|
+
* @param astAdapter DbcAstAdapter instance for source file parsing.
|
|
1158
|
+
*/
|
|
1159
|
+
constructor(e, t) {
|
|
1160
|
+
this._parser = e, this._astAdapter = t;
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* @param filePath Path to the source file to check.
|
|
1164
|
+
* @param [options] Optional lint configuration.
|
|
1165
|
+
* @returns Full lint report with errors and a formatting function.
|
|
1166
|
+
* @see {DbcLinter#lint} in ../../dbc-linter.types.ts
|
|
1167
|
+
*/
|
|
1168
|
+
async lint(e, t) {
|
|
1169
|
+
g.debug(`[DbcTsLinter#lint] [idle → parsing] ${e}`);
|
|
1170
|
+
try {
|
|
1171
|
+
const r = await this._astAdapter.parseFile(
|
|
1172
|
+
e,
|
|
1173
|
+
t?.content
|
|
1174
|
+
);
|
|
1175
|
+
if (!r.ok) {
|
|
1176
|
+
const s = {
|
|
1177
|
+
file: e,
|
|
1178
|
+
line: 1,
|
|
1179
|
+
col: 1,
|
|
1180
|
+
severity: "error",
|
|
1181
|
+
code: N,
|
|
1182
|
+
message: r.error
|
|
1183
|
+
};
|
|
1184
|
+
return g.warn(`[DbcTsLinter#lint] [parsing → parse-failed] ${e}`), { errors: [s], format: () => R([s]) };
|
|
1185
|
+
}
|
|
1186
|
+
const n = this._lintEntities(r.exported, e);
|
|
1187
|
+
return g.info(`[DbcTsLinter#lint] [parsing → linted] ${e} (${n.length} errors)`), {
|
|
1188
|
+
errors: n,
|
|
1189
|
+
format: () => R(n)
|
|
1190
|
+
};
|
|
1191
|
+
} catch (r) {
|
|
1192
|
+
const n = new Error(`[DbcTsLinter#lint] Lint failed for ${e}`, { cause: r });
|
|
1193
|
+
g.error(`[DbcTsLinter#lint] [parsing → failed] ${e}`, {
|
|
1194
|
+
error: n
|
|
1195
|
+
});
|
|
1196
|
+
const s = {
|
|
1197
|
+
file: e,
|
|
1198
|
+
line: 1,
|
|
1199
|
+
col: 1,
|
|
1200
|
+
severity: "error",
|
|
1201
|
+
code: N,
|
|
1202
|
+
message: `Unexpected error: ${String(r)}`
|
|
1203
|
+
};
|
|
1204
|
+
return { errors: [s], format: () => R([s]) };
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* @param filePath Path to the source file to check.
|
|
1209
|
+
* @param [options] Optional lint configuration.
|
|
1210
|
+
* @returns Combined report with errors and count of auto-fixed issues.
|
|
1211
|
+
* @see {DbcLinter#lintAndFix} in ../../dbc-linter.types.ts
|
|
1212
|
+
*/
|
|
1213
|
+
async lintAndFix(e, t) {
|
|
1214
|
+
g.debug(`[DbcTsLinter#lintAndFix] [idle → linting] ${e}`);
|
|
1215
|
+
try {
|
|
1216
|
+
const r = await this.lint(e, t), n = r.errors.length, s = await this._astAdapter.parseFile(e, t?.content);
|
|
1217
|
+
if (!s.ok)
|
|
1218
|
+
return {
|
|
1219
|
+
errors: r.errors,
|
|
1220
|
+
autoFixed: 0,
|
|
1221
|
+
format: () => R(r.errors)
|
|
1222
|
+
};
|
|
1223
|
+
let o = t?.content ?? A(e, "utf8");
|
|
1224
|
+
const a = this._collectContracts(s.exported);
|
|
1225
|
+
let c = !1;
|
|
1226
|
+
for (const h of a) {
|
|
1227
|
+
const p = h.text;
|
|
1228
|
+
let f = p;
|
|
1229
|
+
if (f = this._expandToMultiline(f, h.kind), f = this._removeRedundantTypes(f), f = this._normalizeParamBrackets(f, h.signature), f = this._removeExtraParams(f, h.signature), f = this._removeUnexpectedReturns(f, h.kind, h.signature), f = this._reorderParams(f, h.signature), f = this._reorderTags(f), f = this._normalizeMultiLine(f), f !== p) {
|
|
1230
|
+
let m = f;
|
|
1231
|
+
if (f.includes(`
|
|
1232
|
+
`) && !p.includes(`
|
|
1233
|
+
`)) {
|
|
1234
|
+
const _ = o.indexOf(p), w = _ > 0 ? o.lastIndexOf(`
|
|
1235
|
+
`, _ - 1) : -1, I = o.slice(w + 1, _), b = f.split(`
|
|
1236
|
+
`);
|
|
1237
|
+
for (let T = 1; T < b.length; T += 1)
|
|
1238
|
+
b[T] = I + " " + b[T].trimStart();
|
|
1239
|
+
b[b.length - 1] = I + "*/", m = b.join(`
|
|
1240
|
+
`);
|
|
1241
|
+
}
|
|
1242
|
+
o = o.replace(p, m), c = !0;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
c ? (oe(e, o, "utf8"), g.info(`[DbcTsLinter#lintAndFix] [fixing → written] ${e}`)) : g.info(`[DbcTsLinter#lintAndFix] [linting → clean] ${e} (nothing to fix)`);
|
|
1246
|
+
const d = c ? await this.lint(e, { strategy: "full" }) : r, l = d.errors.length, u = n - l;
|
|
1247
|
+
return g.info(`[DbcTsLinter#lintAndFix] [written → done] ${e} (autoFixed=${u})`), {
|
|
1248
|
+
errors: d.errors,
|
|
1249
|
+
autoFixed: u >= 0 ? u : 0,
|
|
1250
|
+
format: () => R(d.errors)
|
|
1251
|
+
};
|
|
1252
|
+
} catch (r) {
|
|
1253
|
+
const n = new Error(`[DbcTsLinter#lintAndFix] Fix failed for ${e}`, { cause: r });
|
|
1254
|
+
return g.error(`[DbcTsLinter#lintAndFix] [linting → failed] ${e}`, { error: n }), {
|
|
1255
|
+
errors: [
|
|
1256
|
+
{
|
|
1257
|
+
file: e,
|
|
1258
|
+
line: 1,
|
|
1259
|
+
col: 1,
|
|
1260
|
+
severity: "error",
|
|
1261
|
+
code: N,
|
|
1262
|
+
message: `Autofix error: ${String(r)}`
|
|
1263
|
+
}
|
|
1264
|
+
],
|
|
1265
|
+
autoFixed: 0,
|
|
1266
|
+
format: () => R([
|
|
1267
|
+
{
|
|
1268
|
+
file: e,
|
|
1269
|
+
line: 1,
|
|
1270
|
+
col: 1,
|
|
1271
|
+
severity: "error",
|
|
1272
|
+
code: N,
|
|
1273
|
+
message: `Autofix error: ${String(r)}`
|
|
1274
|
+
}
|
|
1275
|
+
])
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
// #region START_LINT_HELPERS
|
|
1280
|
+
/**
|
|
1281
|
+
* @purpose Walk all exported entities and their members, collecting lint errors.
|
|
1282
|
+
* @param entities Exported entities from AST parse result.
|
|
1283
|
+
* @param filePath Source file path for error attribution.
|
|
1284
|
+
* @returns Array of lint errors collected from entities and members.
|
|
1285
|
+
*/
|
|
1286
|
+
_lintEntities(e, t) {
|
|
1287
|
+
const r = [];
|
|
1288
|
+
for (const n of e) {
|
|
1289
|
+
if (!n.contract)
|
|
1290
|
+
r.push(
|
|
1291
|
+
y(
|
|
1292
|
+
W,
|
|
1293
|
+
`Entity '${n.name}' (${n.kind}) is missing a DBC contract`
|
|
1294
|
+
)
|
|
1295
|
+
);
|
|
1296
|
+
else {
|
|
1297
|
+
const s = this._validateContract(
|
|
1298
|
+
n.contract.text,
|
|
1299
|
+
n.signature,
|
|
1300
|
+
n.kind,
|
|
1301
|
+
t,
|
|
1302
|
+
n.contract.startLine,
|
|
1303
|
+
n.contract.startCol
|
|
1304
|
+
);
|
|
1305
|
+
r.push(...s);
|
|
1306
|
+
}
|
|
1307
|
+
for (const s of n.members)
|
|
1308
|
+
if (!s.contract)
|
|
1309
|
+
r.push(
|
|
1310
|
+
y(
|
|
1311
|
+
W,
|
|
1312
|
+
`Member '${s.name}' (${s.kind}) of '${n.name}' is missing a DBC contract`
|
|
1313
|
+
)
|
|
1314
|
+
);
|
|
1315
|
+
else {
|
|
1316
|
+
const o = this._validateContract(
|
|
1317
|
+
s.contract.text,
|
|
1318
|
+
s.signature,
|
|
1319
|
+
s.kind,
|
|
1320
|
+
t,
|
|
1321
|
+
s.contract.startLine,
|
|
1322
|
+
s.contract.startCol
|
|
1323
|
+
);
|
|
1324
|
+
r.push(...o);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
return r.map((n, s) => ({
|
|
1328
|
+
...n,
|
|
1329
|
+
file: t || n.file,
|
|
1330
|
+
line: n.line > 1 ? n.line : s + 1
|
|
1331
|
+
}));
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* @purpose Parse a contract text and validate it, returning both parser issues
|
|
1335
|
+
* (translated per FR-23) and structural mismatches via DbcContractMatchValidator.
|
|
1336
|
+
* @param contractText Raw JSDoc contract text to parse and validate.
|
|
1337
|
+
* @param signature Signature info from AST for parameter/returns matching.
|
|
1338
|
+
* @param kind Entity or member kind for validation matrix lookup.
|
|
1339
|
+
* @param filePath Source file path for error attribution.
|
|
1340
|
+
* @param startLine Contract start line in source for error position.
|
|
1341
|
+
* @param startCol Contract start column in source for error position.
|
|
1342
|
+
* @returns Array of lint errors from parser issues and structural mismatches.
|
|
1343
|
+
*/
|
|
1344
|
+
_validateContract(e, t, r, n, s, o) {
|
|
1345
|
+
const a = [], c = this._parser.parse(e), d = [];
|
|
1346
|
+
for (const u of c.entries)
|
|
1347
|
+
if (d.push(u), u.inline)
|
|
1348
|
+
for (const h of u.inline)
|
|
1349
|
+
d.push(h);
|
|
1350
|
+
for (const u of d)
|
|
1351
|
+
for (const h of u.issues) {
|
|
1352
|
+
const p = s + (h.line ?? 1) - 1;
|
|
1353
|
+
a.push({
|
|
1354
|
+
file: n,
|
|
1355
|
+
line: p > 0 ? p : s,
|
|
1356
|
+
col: o,
|
|
1357
|
+
severity: "error",
|
|
1358
|
+
code: h.code,
|
|
1359
|
+
message: `Parser issue in contract: ${h.code}`
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
const l = Je(d, t, r);
|
|
1363
|
+
for (const u of l)
|
|
1364
|
+
a.push({
|
|
1365
|
+
...u,
|
|
1366
|
+
file: n,
|
|
1367
|
+
line: s,
|
|
1368
|
+
col: o
|
|
1369
|
+
});
|
|
1370
|
+
return a;
|
|
1371
|
+
}
|
|
1372
|
+
// #endregion END_LINT_HELPERS
|
|
1373
|
+
// #region START_AUTOFIX_COLLECT
|
|
1374
|
+
/** @purpose Internal record of a contract block with context for autofix. */
|
|
1375
|
+
_ContractBlock = class {
|
|
1376
|
+
text;
|
|
1377
|
+
kind;
|
|
1378
|
+
signature;
|
|
1379
|
+
constructor(e, t, r) {
|
|
1380
|
+
this.text = e, this.kind = t, this.signature = r;
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
/**
|
|
1384
|
+
* @purpose Walk entities and members, collecting all JSDoc contract blocks
|
|
1385
|
+
* with their associated kind and signature for autofix processing.
|
|
1386
|
+
* @param entities Exported entities from AST parse result.
|
|
1387
|
+
* @returns Array of contract blocks with kind and signature context.
|
|
1388
|
+
*/
|
|
1389
|
+
_collectContracts(e) {
|
|
1390
|
+
const t = [];
|
|
1391
|
+
for (const r of e) {
|
|
1392
|
+
r.contract && t.push(new this._ContractBlock(r.contract.text, r.kind, r.signature));
|
|
1393
|
+
for (const n of r.members)
|
|
1394
|
+
n.contract && t.push(new this._ContractBlock(n.contract.text, n.kind, n.signature));
|
|
1395
|
+
}
|
|
1396
|
+
return t;
|
|
1397
|
+
}
|
|
1398
|
+
// #endregion END_AUTOFIX_COLLECT
|
|
1399
|
+
// #region START_AUTOFIX_CHAIN_METHODS
|
|
1400
|
+
/**
|
|
1401
|
+
* @purpose Expand single-line pipe contracts to multi-line for callable kinds or >3 tags.
|
|
1402
|
+
* @param jsdocText Raw JSDoc comment text.
|
|
1403
|
+
* @param [kind] Entity kind — callable kinds or pipe with >3 tags are expanded.
|
|
1404
|
+
* @returns JSDoc text in multi-line format if applicable.
|
|
1405
|
+
*/
|
|
1406
|
+
_expandToMultiline(e, t) {
|
|
1407
|
+
if (e.includes(`
|
|
1408
|
+
`) || !e.includes(" | @")) return e;
|
|
1409
|
+
const n = e.replace(/^\s*\/\*\*\s*/, "").replace(/\s*\*\/\s*$/, "").split(" | @");
|
|
1410
|
+
if (n.length === 0 || n.length <= 3 && !F.has(t ?? "")) return e;
|
|
1411
|
+
const o = e.match(/^(\s*)/), a = o ? o[1] : "", c = a + " ", d = [a + "/**"];
|
|
1412
|
+
d.push(c + "* " + (n[0] ?? "").trim());
|
|
1413
|
+
for (let l = 1; l < n.length; l += 1)
|
|
1414
|
+
d.push(c + "* @" + (n[l] ?? "").trim());
|
|
1415
|
+
return d.push(a + " */"), d.join(`
|
|
1416
|
+
`);
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* @purpose Remove redundant `{dataType}` annotations from @param and @returns tags.
|
|
1420
|
+
* @param jsdocText Raw JSDoc comment text.
|
|
1421
|
+
* @returns JSDoc text with types stripped.
|
|
1422
|
+
*/
|
|
1423
|
+
_removeRedundantTypes(e) {
|
|
1424
|
+
return e.replace(/(\*?\s*)@(param|returns)\s+\{[^}]*\}\s+/g, "$1@$2 ");
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* @purpose Adjust @param specifiers to wrap optional params in `[brackets]` and remove brackets from required params.
|
|
1428
|
+
* @param jsdocText Raw JSDoc comment text.
|
|
1429
|
+
* @param signature Signature with optionality flags.
|
|
1430
|
+
* @returns JSDoc text with normalized brackets.
|
|
1431
|
+
*/
|
|
1432
|
+
_normalizeParamBrackets(e, t) {
|
|
1433
|
+
const r = new Map(t.params.map((o) => [o.name, o]));
|
|
1434
|
+
if (!e.includes(`
|
|
1435
|
+
`)) {
|
|
1436
|
+
const o = e.match(/^(\s*)/), a = o ? o[1] : "", l = e.replace(/^\s*\/\*\*\s*/, "").replace(/\s*\*\/\s*$/, "").split(" | ").map((u) => {
|
|
1437
|
+
const h = u.trim();
|
|
1438
|
+
if (!/^@param\s/.test(h)) return u;
|
|
1439
|
+
const p = this._extractParamSpecifier(h), f = x(p), m = r.get(f);
|
|
1440
|
+
if (!m || m.isRest) return u;
|
|
1441
|
+
const _ = m.optional, w = p.startsWith("[") && p.endsWith("]");
|
|
1442
|
+
if (_ === w) return u;
|
|
1443
|
+
let I;
|
|
1444
|
+
_ ? I = p.startsWith("...") ? "[..." + f + "]" : "[" + f + "]" : I = p.startsWith("...") ? "..." + f : f;
|
|
1445
|
+
const b = p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1446
|
+
return u.replace(
|
|
1447
|
+
new RegExp(`(@param\\s+(?:\\{[^}]*\\}\\s+)?)${b}`),
|
|
1448
|
+
`$1${I}`
|
|
1449
|
+
);
|
|
1450
|
+
});
|
|
1451
|
+
return a + "/** " + l.join(" | ") + " */";
|
|
1452
|
+
}
|
|
1453
|
+
const n = e.split(`
|
|
1454
|
+
`), s = [];
|
|
1455
|
+
for (const o of n) {
|
|
1456
|
+
let a = o.trim();
|
|
1457
|
+
if (a.startsWith("* ") ? a = a.slice(2).trimStart() : a.startsWith("*") && (a = a.slice(1).trimStart()), !/^@param\s/.test(a)) {
|
|
1458
|
+
s.push(o);
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
const c = this._extractParamSpecifier(a), d = x(c), l = r.get(d);
|
|
1462
|
+
if (!l || l.isRest) {
|
|
1463
|
+
s.push(o);
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
const u = l.optional, h = c.startsWith("[") && c.endsWith("]");
|
|
1467
|
+
if (u === h) {
|
|
1468
|
+
s.push(o);
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
let p;
|
|
1472
|
+
u ? p = c.startsWith("...") ? "[..." + d + "]" : "[" + d + "]" : p = c.startsWith("...") ? "..." + d : d;
|
|
1473
|
+
const f = c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), m = o.replace(
|
|
1474
|
+
new RegExp(`(@param\\s+(?:\\{[^}]*\\}\\s+)?)${f}`),
|
|
1475
|
+
`$1${p}`
|
|
1476
|
+
);
|
|
1477
|
+
s.push(m);
|
|
1478
|
+
}
|
|
1479
|
+
return s.join(`
|
|
1480
|
+
`);
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* @purpose Remove @param entries whose specifier does not match any signature parameter. | Handles both multi-line and single-line contracts.
|
|
1484
|
+
* @param jsdocText Raw JSDoc comment text.
|
|
1485
|
+
* @param signature Signature to match against.
|
|
1486
|
+
* @returns JSDoc text with extra params removed.
|
|
1487
|
+
*/
|
|
1488
|
+
_removeExtraParams(e, t) {
|
|
1489
|
+
const r = new Set(t.params.map((a) => a.name)), n = e.split(`
|
|
1490
|
+
`), s = [];
|
|
1491
|
+
let o = !1;
|
|
1492
|
+
for (const a of n) {
|
|
1493
|
+
let c = a.trim();
|
|
1494
|
+
if (c === "*/" || c === "*/ ") {
|
|
1495
|
+
o = !1, s.push(a);
|
|
1496
|
+
continue;
|
|
1497
|
+
}
|
|
1498
|
+
if (c.startsWith("* ") ? c = c.slice(2).trimStart() : c.startsWith("*") && (c = c.slice(1).trimStart()), /^@param\s/.test(c)) {
|
|
1499
|
+
const d = this._extractParamSpecifier(c), l = x(d);
|
|
1500
|
+
r.has(l) ? (o = !1, s.push(a)) : o = !0;
|
|
1501
|
+
} else /^@\w/.test(c) ? (o = !1, s.push(a)) : o || s.push(a);
|
|
1502
|
+
}
|
|
1503
|
+
return s.join(`
|
|
1504
|
+
`);
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* @purpose Remove @returns tag when the kind does not allow it or return type is void.
|
|
1508
|
+
* @param jsdocText Raw JSDoc comment text.
|
|
1509
|
+
* @param kind Entity or member kind.
|
|
1510
|
+
* @param [signature] Optional signature info to check void return type.
|
|
1511
|
+
* @returns JSDoc text with unexpected @returns removed.
|
|
1512
|
+
*/
|
|
1513
|
+
_removeUnexpectedReturns(e, t, r) {
|
|
1514
|
+
if (!(B.has(t) || U.has(t) && r?.returnType === "void"))
|
|
1515
|
+
return e;
|
|
1516
|
+
const s = e.split(`
|
|
1517
|
+
`), o = [];
|
|
1518
|
+
let a = !1;
|
|
1519
|
+
for (const c of s) {
|
|
1520
|
+
let d = c.trim();
|
|
1521
|
+
if (d === "*/" || d === "*/ ") {
|
|
1522
|
+
a = !1, o.push(c);
|
|
1523
|
+
continue;
|
|
1524
|
+
}
|
|
1525
|
+
d.startsWith("* ") ? d = d.slice(2).trimStart() : d.startsWith("*") && (d = d.slice(1).trimStart()), /^@returns\b/.test(d) ? a = !0 : /^@\w/.test(d) ? (a = !1, o.push(c)) : a || o.push(c);
|
|
1526
|
+
}
|
|
1527
|
+
return o.join(`
|
|
1528
|
+
`);
|
|
1529
|
+
}
|
|
1530
|
+
/**
|
|
1531
|
+
* @purpose Reorder @param entries to match the signature parameter order.
|
|
1532
|
+
* @param jsdocText Raw JSDoc comment text.
|
|
1533
|
+
* @param signature Signature with desired parameter order.
|
|
1534
|
+
* @returns JSDoc text with params reordered.
|
|
1535
|
+
*/
|
|
1536
|
+
_reorderParams(e, t) {
|
|
1537
|
+
if (t.params.length === 0)
|
|
1538
|
+
return e;
|
|
1539
|
+
const r = t.params.map((f) => f.name), n = e.split(`
|
|
1540
|
+
`), s = [], o = [], a = [];
|
|
1541
|
+
let c = !1, d = !1, l = null;
|
|
1542
|
+
for (const f of n) {
|
|
1543
|
+
let m = f.trim();
|
|
1544
|
+
if (m.startsWith("* ") ? m = m.slice(2).trimStart() : m.startsWith("*") && (m = m.slice(1).trimStart()), !c && !d && /^@param\s/.test(m) && (c = !0), !c && !d) {
|
|
1545
|
+
if (o.push(f), /^@param\s/.test(m)) {
|
|
1546
|
+
c = !0;
|
|
1547
|
+
const _ = this._extractParamSpecifier(m);
|
|
1548
|
+
l = {
|
|
1549
|
+
name: x(_),
|
|
1550
|
+
lines: [f]
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1555
|
+
if (c)
|
|
1556
|
+
if (/^@param\s/.test(m)) {
|
|
1557
|
+
l && s.push(l);
|
|
1558
|
+
const _ = this._extractParamSpecifier(m);
|
|
1559
|
+
l = {
|
|
1560
|
+
name: x(_),
|
|
1561
|
+
lines: [f]
|
|
1562
|
+
};
|
|
1563
|
+
} else /^@\w/.test(m) ? (l && (s.push(l), l = null), c = !1, d = !0, a.push(f)) : l && l.lines.push(f);
|
|
1564
|
+
else d && a.push(f);
|
|
1565
|
+
}
|
|
1566
|
+
l && s.push(l);
|
|
1567
|
+
const u = /* @__PURE__ */ new Map();
|
|
1568
|
+
for (const f of s)
|
|
1569
|
+
u.set(f.name, f);
|
|
1570
|
+
const h = [];
|
|
1571
|
+
for (const f of r) {
|
|
1572
|
+
const m = u.get(f);
|
|
1573
|
+
m && (h.push(m), u.delete(f));
|
|
1574
|
+
}
|
|
1575
|
+
for (const [, f] of u)
|
|
1576
|
+
h.push(f);
|
|
1577
|
+
const p = [...o];
|
|
1578
|
+
for (const f of h)
|
|
1579
|
+
p.push(...f.lines);
|
|
1580
|
+
return p.push(...a), p.join(`
|
|
1581
|
+
`);
|
|
1582
|
+
}
|
|
1583
|
+
/**
|
|
1584
|
+
* @purpose Reorder contract tags to canonical DBC order. | Order: description → purpose → implements → invariant → pre → param(*) → | throws → returns → post → sideEffect → other tags.
|
|
1585
|
+
* @param jsdocText Raw JSDoc comment text.
|
|
1586
|
+
* @returns JSDoc text with tags in canonical order.
|
|
1587
|
+
*/
|
|
1588
|
+
_reorderTags(e) {
|
|
1589
|
+
const t = /* @__PURE__ */ new Map([
|
|
1590
|
+
["purpose", 0],
|
|
1591
|
+
["implements", 1],
|
|
1592
|
+
["invariant", 2],
|
|
1593
|
+
["pre", 3],
|
|
1594
|
+
["param", 4],
|
|
1595
|
+
["throws", 5],
|
|
1596
|
+
["returns", 6],
|
|
1597
|
+
["post", 7],
|
|
1598
|
+
["sideEffect", 8],
|
|
1599
|
+
["see", 9],
|
|
1600
|
+
["consumer", 9],
|
|
1601
|
+
["consumers", 9],
|
|
1602
|
+
["author", 9],
|
|
1603
|
+
["deprecated", 9]
|
|
1604
|
+
]), r = e.split(`
|
|
1605
|
+
`), n = [], s = [];
|
|
1606
|
+
let o = null, a = !1, c = null;
|
|
1607
|
+
for (const u of r) {
|
|
1608
|
+
let h = u.trim();
|
|
1609
|
+
if (h === "*/" || h === "*/ ") {
|
|
1610
|
+
c = u;
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
h.startsWith("* ") ? h = h.slice(2).trimStart() : h.startsWith("*") && (h = h.slice(1).trimStart());
|
|
1614
|
+
const p = h.match(/^@(\w+)/);
|
|
1615
|
+
if (!a && !p)
|
|
1616
|
+
n.push(u);
|
|
1617
|
+
else if (p) {
|
|
1618
|
+
a = !0;
|
|
1619
|
+
const f = p[1];
|
|
1620
|
+
o && s.push(o), o = {
|
|
1621
|
+
tag: f,
|
|
1622
|
+
order: t.get(f) ?? 99,
|
|
1623
|
+
lines: [u]
|
|
1624
|
+
};
|
|
1625
|
+
} else a && o ? o.lines.push(u) : n.push(u);
|
|
1626
|
+
}
|
|
1627
|
+
o && s.push(o);
|
|
1628
|
+
const d = s.map((u, h) => ({ ...u, origIdx: h }));
|
|
1629
|
+
d.sort((u, h) => u.order !== h.order ? u.order - h.order : u.origIdx - h.origIdx);
|
|
1630
|
+
const l = [...n];
|
|
1631
|
+
for (const u of d)
|
|
1632
|
+
l.push(...u.lines);
|
|
1633
|
+
return c !== null && l.push(c), l.join(`
|
|
1634
|
+
`);
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* @purpose Normalize multi-line JSDoc format: ensure closing marker is on its own line | and opening marker is bare (no content after it). | Preserves original indentation by patching specific format issues | rather than reconstructing the entire block.
|
|
1638
|
+
* @invariant Single-line contracts pass through unchanged.
|
|
1639
|
+
* @param jsdocText Raw JSDoc comment text.
|
|
1640
|
+
* @returns Normalized JSDoc text.
|
|
1641
|
+
*/
|
|
1642
|
+
_normalizeMultiLine(e) {
|
|
1643
|
+
if (!e.includes(`
|
|
1644
|
+
`))
|
|
1645
|
+
return e;
|
|
1646
|
+
const t = e.split(`
|
|
1647
|
+
`), n = t[0].match(/^(\s*\/\*\*)\s+(.+)/);
|
|
1648
|
+
if (n) {
|
|
1649
|
+
t[0] = n[1];
|
|
1650
|
+
const c = this._detectContentIndent(t);
|
|
1651
|
+
t.splice(1, 0, c + "* " + n[2]);
|
|
1652
|
+
}
|
|
1653
|
+
const s = t.length - 1, a = t[s].match(/^(\s*)\*\s+(.+?)\s*\*\/\s*$/);
|
|
1654
|
+
if (a) {
|
|
1655
|
+
const c = a[1];
|
|
1656
|
+
t[s] = c + "* " + a[2];
|
|
1657
|
+
const d = c.length > 0 ? c.slice(0, -1) : "";
|
|
1658
|
+
t.push(d + " */");
|
|
1659
|
+
}
|
|
1660
|
+
for (let c = 1; c < t.length - 1; c += 1) {
|
|
1661
|
+
const d = t[c];
|
|
1662
|
+
if (d.trim() !== "" && !/^\s*\*\s/.test(d)) {
|
|
1663
|
+
const l = d.match(/^(\s*)/), u = l ? l[1] : "";
|
|
1664
|
+
t[c] = u + "* " + d.trimStart();
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
return t.join(`
|
|
1668
|
+
`);
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* @purpose Detect the indentation pattern used by content lines. | Returns the whitespace prefix before `*` on the first content line, | or the indent of the opening line as fallback.
|
|
1672
|
+
* @param lines All lines of the JSDoc block (may be mutated by Step 1).
|
|
1673
|
+
* @returns Indent string for `*`-prefixed content lines.
|
|
1674
|
+
*/
|
|
1675
|
+
_detectContentIndent(e) {
|
|
1676
|
+
for (let r = 1; r < e.length - 1; r += 1) {
|
|
1677
|
+
const n = e[r].match(/^(\s*)\*\s/);
|
|
1678
|
+
if (n)
|
|
1679
|
+
return n[1];
|
|
1680
|
+
}
|
|
1681
|
+
return (e[0].match(/^(\s*)/)?.[1] ?? "") + " ";
|
|
1682
|
+
}
|
|
1683
|
+
/**
|
|
1684
|
+
* @purpose Attempt to convert a multi-line JSDoc comment to single-line format. | Inlines only single-tag contracts (1 @tag); multi-tag contracts stay multi-line. | Performs a dry-run through the parser: if the inline version introduces new | parse errors, the original multi-line text is returned unchanged.
|
|
1685
|
+
* @param jsdocText Raw JSDoc comment text.
|
|
1686
|
+
* @returns Inline text if safe, otherwise the original text.
|
|
1687
|
+
*/
|
|
1688
|
+
_inlineIfSafe(e) {
|
|
1689
|
+
if (!e.includes(`
|
|
1690
|
+
`))
|
|
1691
|
+
return e;
|
|
1692
|
+
const t = e.replace(/^\/\*\*\s*/, "").replace(/\s*\*\/\s*$/, "").split(`
|
|
1693
|
+
`).map((d) => d.replace(/^\s*\*\s?/, "").trim()).filter((d) => d.length > 0);
|
|
1694
|
+
if (t.length === 0 || t.filter((d) => /^@\w/.test(d)).length > 1)
|
|
1695
|
+
return e;
|
|
1696
|
+
const n = e.match(/^(\s*)/), o = (n ? n[1] : "") + "/** " + t.join(" | ") + " */";
|
|
1697
|
+
return this._parser.parse(o).entries.some((d) => d.issues.length > 0) ? e : o;
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* @purpose Extract the specifier (parameter name) from a @param tag line. | Example: `@param userId Description` → `userId`
|
|
1701
|
+
* @param line Trimmed line content starting with `@param`.
|
|
1702
|
+
* @returns The specifier or empty string.
|
|
1703
|
+
*/
|
|
1704
|
+
_extractParamSpecifier(e) {
|
|
1705
|
+
const n = e.replace(/^(\*?\s*)?@param\s+/, "").replace(/^\{[^}]*\}\s+/, "").match(/^(\S+)/);
|
|
1706
|
+
return n ? n[1] : "";
|
|
1707
|
+
}
|
|
1708
|
+
// #endregion END_AUTOFIX_CHAIN_METHODS
|
|
1709
|
+
}
|
|
1710
|
+
class St {
|
|
1711
|
+
/** @purpose Lazy-initialized tree-sitter parser instance */
|
|
1712
|
+
_parser;
|
|
1713
|
+
/**
|
|
1714
|
+
* @param filePath Absolute path to the TypeScript source file.
|
|
1715
|
+
* @param [content] Optional pre-loaded file content, bypasses disk read.
|
|
1716
|
+
* @returns Parse result with exported entities or an error.
|
|
1717
|
+
* @see {DbcAstAdapter#parseFile} in ../../dbc-ast-adapter.types.ts
|
|
1718
|
+
*/
|
|
1719
|
+
async parseFile(e, t) {
|
|
1720
|
+
try {
|
|
1721
|
+
g.debug(`[DbcTsAstAdapter#parseFile] [idle → reading] ${e}`);
|
|
1722
|
+
const r = t ?? A(e, "utf8"), s = (await this._initializeParser()).parse(r);
|
|
1723
|
+
if (s.rootNode.hasError) {
|
|
1724
|
+
const c = this._findErrorNode(s.rootNode), d = c ? ` at line ${c.startPosition.row + 1}` : "";
|
|
1725
|
+
return g.warn(`[DbcTsAstAdapter#parseFile] [reading → parse-failed] ${e}${d}`), { ok: !1, error: `Syntax error in ${e}${d}` };
|
|
1726
|
+
}
|
|
1727
|
+
const a = this._extractExported(s.rootNode, r);
|
|
1728
|
+
return g.info(
|
|
1729
|
+
`[DbcTsAstAdapter#parseFile] [reading → parsed] ${e} (${a.length} entities)`
|
|
1730
|
+
), { ok: !0, exported: a };
|
|
1731
|
+
} catch (r) {
|
|
1732
|
+
if (r instanceof Error && "code" in r && r.code === "ENOENT")
|
|
1733
|
+
return g.warn(`[DbcTsAstAdapter#parseFile] [reading → not-found] ${e}`), { ok: !1, error: `File not found: ${e}` };
|
|
1734
|
+
const s = new Error(`[DbcTsAstAdapter#parseFile] Parse failed for ${e}`, {
|
|
1735
|
+
cause: r
|
|
1736
|
+
});
|
|
1737
|
+
return g.error(`[DbcTsAstAdapter#parseFile] [reading → failed] ${e}`, { error: s }), { ok: !1, error: `Failed to parse ${e}: ${String(r)}` };
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
/**
|
|
1741
|
+
* @purpose Lazy-initializes the tree-sitter Parser with the TypeScript grammar.
|
|
1742
|
+
* Uses dynamic import so tree-sitter is only loaded when the adapter is actually used.
|
|
1743
|
+
* @returns Configured Parser instance.
|
|
1744
|
+
*/
|
|
1745
|
+
async _initializeParser() {
|
|
1746
|
+
if (!this._parser) {
|
|
1747
|
+
const [e, t] = await Promise.all([
|
|
1748
|
+
import("tree-sitter"),
|
|
1749
|
+
import("./index-DAU6h0aT.js").then((o) => o.i)
|
|
1750
|
+
]), r = e.default, n = t.default;
|
|
1751
|
+
this._parser = new r();
|
|
1752
|
+
const s = n.tsx ?? n;
|
|
1753
|
+
this._parser.setLanguage(s);
|
|
1754
|
+
}
|
|
1755
|
+
return this._parser;
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* @purpose Walks the root AST node and extracts all exported entities,
|
|
1759
|
+
* skipping re-exports.
|
|
1760
|
+
* @param root Root CST node.
|
|
1761
|
+
* @param source Full source text for slicing.
|
|
1762
|
+
* @returns Array of exported entities.
|
|
1763
|
+
*/
|
|
1764
|
+
_extractExported(e, t) {
|
|
1765
|
+
const r = [];
|
|
1766
|
+
let n;
|
|
1767
|
+
for (let s = 0; s < e.childCount; s += 1) {
|
|
1768
|
+
const o = e.child(s);
|
|
1769
|
+
if (o) {
|
|
1770
|
+
if (o.type === "comment") {
|
|
1771
|
+
const a = t.slice(o.startIndex, o.endIndex);
|
|
1772
|
+
a.startsWith("/**") ? n = {
|
|
1773
|
+
text: a,
|
|
1774
|
+
startLine: o.startPosition.row + 1,
|
|
1775
|
+
startCol: o.startPosition.column + 1
|
|
1776
|
+
} : n = void 0;
|
|
1777
|
+
continue;
|
|
1778
|
+
}
|
|
1779
|
+
if (o.type === "export_statement") {
|
|
1780
|
+
const a = this._extractExportEntity(o, t, n);
|
|
1781
|
+
a && r.push(a), n = void 0;
|
|
1782
|
+
continue;
|
|
1783
|
+
}
|
|
1784
|
+
o.type !== "comment" && (n = void 0);
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
return r;
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* @purpose Extracts a single exported entity from an export_statement node.
|
|
1791
|
+
* Skips re-exports.
|
|
1792
|
+
* @param node export_statement node.
|
|
1793
|
+
* @param source Full source text.
|
|
1794
|
+
* @param pendingContract JSDoc contract that precedes this export, if any.
|
|
1795
|
+
* @returns Exported entity or undefined for re-exports.
|
|
1796
|
+
*/
|
|
1797
|
+
_extractExportEntity(e, t, r) {
|
|
1798
|
+
if (this._isReExport(e))
|
|
1799
|
+
return;
|
|
1800
|
+
let n = !1, s = null;
|
|
1801
|
+
for (let h = 0; h < e.childCount; h += 1) {
|
|
1802
|
+
const p = e.child(h);
|
|
1803
|
+
if (p) {
|
|
1804
|
+
if (p.type === "default") {
|
|
1805
|
+
n = !0;
|
|
1806
|
+
continue;
|
|
1807
|
+
}
|
|
1808
|
+
p.type === "export" || p.type === ";" || !s && p.type !== "export" && p.type !== ";" && (s = p);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
if (!s)
|
|
1812
|
+
return;
|
|
1813
|
+
const o = this._mapKind(s.type, n), a = this._extractName(s, t, n), c = this._extractSignature(s, t), d = this._extractMembers(s, t, o), l = o === "class" ? this._hasImplements(s) : void 0, u = r ? {
|
|
1814
|
+
text: r.text,
|
|
1815
|
+
startLine: r.startLine,
|
|
1816
|
+
startCol: r.startCol
|
|
1817
|
+
} : void 0;
|
|
1818
|
+
return {
|
|
1819
|
+
name: a,
|
|
1820
|
+
kind: o,
|
|
1821
|
+
members: d,
|
|
1822
|
+
contract: u,
|
|
1823
|
+
signature: c,
|
|
1824
|
+
implementsInterfaces: l
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* @purpose Determines whether an export_statement is a re-export.
|
|
1829
|
+
* @param node export_statement node.
|
|
1830
|
+
* @returns True if the export is a re-export that should be skipped.
|
|
1831
|
+
*/
|
|
1832
|
+
_isReExport(e) {
|
|
1833
|
+
for (let t = 0; t < e.childCount; t += 1) {
|
|
1834
|
+
const r = e.child(t);
|
|
1835
|
+
if (r && (r.type === "export_clause" || r.type === "*"))
|
|
1836
|
+
return !0;
|
|
1837
|
+
}
|
|
1838
|
+
return !1;
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* @purpose Maps a declaration node type to an entity kind string.
|
|
1842
|
+
* @param nodeType tree-sitter node type.
|
|
1843
|
+
* @param isDefault Whether this is a default export.
|
|
1844
|
+
* @returns Entity kind.
|
|
1845
|
+
*/
|
|
1846
|
+
_mapKind(e, t) {
|
|
1847
|
+
switch (e) {
|
|
1848
|
+
case "function_declaration":
|
|
1849
|
+
case "generator_function_declaration":
|
|
1850
|
+
return "function";
|
|
1851
|
+
case "class_declaration":
|
|
1852
|
+
return "class";
|
|
1853
|
+
case "interface_declaration":
|
|
1854
|
+
return "interface";
|
|
1855
|
+
case "type_alias_declaration":
|
|
1856
|
+
return "type";
|
|
1857
|
+
case "enum_declaration":
|
|
1858
|
+
return "enum";
|
|
1859
|
+
case "lexical_declaration":
|
|
1860
|
+
case "variable_declaration":
|
|
1861
|
+
return "const";
|
|
1862
|
+
default:
|
|
1863
|
+
return t ? "export-default" : e;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
/**
|
|
1867
|
+
* @purpose Extracts the entity name from a declaration node.
|
|
1868
|
+
* @param node Declaration node.
|
|
1869
|
+
* @param source Full source text.
|
|
1870
|
+
* @param isDefault Whether this is a default export.
|
|
1871
|
+
* @returns Entity name.
|
|
1872
|
+
*/
|
|
1873
|
+
_extractName(e, t, r) {
|
|
1874
|
+
const n = e.childForFieldName?.("name") ?? null;
|
|
1875
|
+
if (n)
|
|
1876
|
+
return t.slice(n.startIndex, n.endIndex);
|
|
1877
|
+
for (let s = 0; s < e.childCount; s += 1) {
|
|
1878
|
+
const o = e.child(s);
|
|
1879
|
+
if (o) {
|
|
1880
|
+
if (o.type === "variable_declarator") {
|
|
1881
|
+
const a = o.child(0);
|
|
1882
|
+
if (a)
|
|
1883
|
+
return t.slice(a.startIndex, a.endIndex);
|
|
1884
|
+
}
|
|
1885
|
+
if (o.type === "identifier" && r)
|
|
1886
|
+
return t.slice(o.startIndex, o.endIndex);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
return r ? "_default" : "unknown";
|
|
1890
|
+
}
|
|
1891
|
+
/**
|
|
1892
|
+
* @purpose Extracts signature info (params + returnType) from a callable declaration.
|
|
1893
|
+
* @param node Declaration node.
|
|
1894
|
+
* @param source Full source text.
|
|
1895
|
+
* @returns Signature info with empty arrays for non-callable entities.
|
|
1896
|
+
*/
|
|
1897
|
+
_extractSignature(e, t) {
|
|
1898
|
+
const r = [];
|
|
1899
|
+
let n = "void";
|
|
1900
|
+
const s = this._findChild(e, "formal_parameters");
|
|
1901
|
+
if (s)
|
|
1902
|
+
for (let a = 0; a < s.childCount; a += 1) {
|
|
1903
|
+
const c = s.child(a);
|
|
1904
|
+
c && (c.type === "required_parameter" || c.type === "optional_parameter") && r.push(this._extractParam(c, t));
|
|
1905
|
+
}
|
|
1906
|
+
if (e.type === "function_type") {
|
|
1907
|
+
const a = [];
|
|
1908
|
+
for (let d = 0; d < e.childCount; d += 1) {
|
|
1909
|
+
const l = e.child(d);
|
|
1910
|
+
l && a.push(l);
|
|
1911
|
+
}
|
|
1912
|
+
const c = a.findIndex((d) => d.type === "=>");
|
|
1913
|
+
return c >= 0 && c + 1 < a.length && (n = t.slice(a[c + 1].startIndex, a[c + 1].endIndex).trim(), n || (n = "unknown")), { params: r, returnType: n };
|
|
1914
|
+
}
|
|
1915
|
+
const o = e.childForFieldName?.("return_type") ?? null;
|
|
1916
|
+
return o && (n = t.slice(o.startIndex + 1, o.endIndex).trim(), n || (n = "unknown")), r.length === 0 && !o && (n = "void"), { params: r, returnType: n };
|
|
1917
|
+
}
|
|
1918
|
+
/**
|
|
1919
|
+
* @purpose Extracts a single parameter (required or optional) from the formal_parameters list.
|
|
1920
|
+
* @param paramNode required_parameter or optional_parameter node.
|
|
1921
|
+
* @param source Full source text.
|
|
1922
|
+
* @returns Parameter info.
|
|
1923
|
+
*/
|
|
1924
|
+
_extractParam(e, t) {
|
|
1925
|
+
let r = "", n = "any", s = e.type === "optional_parameter", o = !1;
|
|
1926
|
+
for (let a = 0; a < e.childCount; a += 1) {
|
|
1927
|
+
const c = e.child(a);
|
|
1928
|
+
if (c) {
|
|
1929
|
+
if (c.type === "rest_pattern") {
|
|
1930
|
+
o = !0;
|
|
1931
|
+
for (let d = 0; d < c.childCount; d += 1) {
|
|
1932
|
+
const l = c.child(d);
|
|
1933
|
+
l && (l.type === "identifier" || l.type === "object_pattern" || l.type === "array_pattern") && (r = t.slice(l.startIndex, l.endIndex));
|
|
1934
|
+
}
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1937
|
+
if (c.type === "...") {
|
|
1938
|
+
o = !0;
|
|
1939
|
+
continue;
|
|
1940
|
+
}
|
|
1941
|
+
if (c.type === "?") {
|
|
1942
|
+
s = !0;
|
|
1943
|
+
continue;
|
|
1944
|
+
}
|
|
1945
|
+
if (c.type === "=") {
|
|
1946
|
+
s = !0;
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
if (c.type === "identifier" || c.type === "object_pattern" || c.type === "array_pattern") {
|
|
1950
|
+
r = t.slice(c.startIndex, c.endIndex);
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
if (c.type === "type_annotation") {
|
|
1954
|
+
for (let d = 0; d < c.childCount; d += 1) {
|
|
1955
|
+
const l = c.child(d);
|
|
1956
|
+
l && l.type !== ":" && (n = t.slice(l.startIndex, l.endIndex));
|
|
1957
|
+
}
|
|
1958
|
+
continue;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
return { name: r, type: n, optional: s, isRest: o };
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* @purpose Extracts members of a class, interface, or enum entity.
|
|
1966
|
+
* @param node Declaration node.
|
|
1967
|
+
* @param source Full source text.
|
|
1968
|
+
* @param kind Entity kind.
|
|
1969
|
+
* @returns Array of members (empty for non-collection entities).
|
|
1970
|
+
*/
|
|
1971
|
+
_extractMembers(e, t, r) {
|
|
1972
|
+
return r === "class" ? this._extractClassMembers(e, t) : r === "interface" ? this._extractInterfaceMembers(e, t) : r === "enum" ? this._extractEnumMembers(e, t) : r === "type" ? this._extractTypeAliasMembers(e, t) : [];
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* @purpose Extracts members from a class declaration body.
|
|
1976
|
+
* @param node class_declaration node.
|
|
1977
|
+
* @param source Full source text.
|
|
1978
|
+
* @returns Array of class members.
|
|
1979
|
+
*/
|
|
1980
|
+
_extractClassMembers(e, t) {
|
|
1981
|
+
const r = e.childForFieldName?.("body") ?? null;
|
|
1982
|
+
if (!r) return [];
|
|
1983
|
+
const n = [];
|
|
1984
|
+
let s;
|
|
1985
|
+
for (let o = 0; o < r.childCount; o += 1) {
|
|
1986
|
+
const a = r.child(o);
|
|
1987
|
+
if (!a) continue;
|
|
1988
|
+
if (a.type === "comment") {
|
|
1989
|
+
const u = t.slice(a.startIndex, a.endIndex);
|
|
1990
|
+
u.startsWith("/**") ? s = {
|
|
1991
|
+
text: u,
|
|
1992
|
+
startLine: a.startPosition.row + 1,
|
|
1993
|
+
startCol: a.startPosition.column + 1
|
|
1994
|
+
} : s = void 0;
|
|
1995
|
+
continue;
|
|
1996
|
+
}
|
|
1997
|
+
if (a.type === "comment") {
|
|
1998
|
+
s = void 0;
|
|
1999
|
+
continue;
|
|
2000
|
+
}
|
|
2001
|
+
let c, d = "", l = null;
|
|
2002
|
+
if (a.type === "method_definition" ? (d = this._extractMethodName(a, t), d === "constructor" ? c = "constructor" : this._hasToken(a, "get") ? c = "getter" : this._hasToken(a, "set") ? c = "setter" : c = "method", l = a) : a.type === "public_field_definition" && (d = this._extractFieldName(a, t), c = "field"), c && d) {
|
|
2003
|
+
const u = l ? this._extractSignature(l, t) : { params: [], returnType: "void" }, h = s ? {
|
|
2004
|
+
text: s.text,
|
|
2005
|
+
startLine: s.startLine,
|
|
2006
|
+
startCol: s.startCol
|
|
2007
|
+
} : void 0;
|
|
2008
|
+
n.push({ name: d, kind: c, contract: h, signature: u }), s = void 0;
|
|
2009
|
+
} else
|
|
2010
|
+
s = void 0;
|
|
2011
|
+
}
|
|
2012
|
+
return n;
|
|
2013
|
+
}
|
|
2014
|
+
/**
|
|
2015
|
+
* @purpose Extracts members from an interface declaration body.
|
|
2016
|
+
* @param node interface_declaration node.
|
|
2017
|
+
* @param source Full source text.
|
|
2018
|
+
* @returns Array of interface members.
|
|
2019
|
+
*/
|
|
2020
|
+
_extractInterfaceMembers(e, t) {
|
|
2021
|
+
const r = e.childForFieldName?.("body") ?? null;
|
|
2022
|
+
return r ? this._extractObjectTypeMembers(r, t) : [];
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* @purpose Extracts members from a type alias with an object type literal body.
|
|
2026
|
+
* Delegates to the same logic as interface members since object_type ≡ interface body.
|
|
2027
|
+
* @param node type_alias_declaration node.
|
|
2028
|
+
* @param source Full source text.
|
|
2029
|
+
* @returns Array of object type members.
|
|
2030
|
+
*/
|
|
2031
|
+
_extractTypeAliasMembers(e, t) {
|
|
2032
|
+
for (let r = 0; r < e.childCount; r += 1) {
|
|
2033
|
+
const n = e.child(r);
|
|
2034
|
+
if (n?.type === "object_type")
|
|
2035
|
+
return this._extractObjectTypeMembers(n, t);
|
|
2036
|
+
}
|
|
2037
|
+
return [];
|
|
2038
|
+
}
|
|
2039
|
+
/**
|
|
2040
|
+
* @purpose Extracts members from an object_type or interface body node.
|
|
2041
|
+
* Shared by type alias (object_type) and interface declarations.
|
|
2042
|
+
* @param body object_type or interface_body node.
|
|
2043
|
+
* @param source Full source text.
|
|
2044
|
+
* @returns Array of members.
|
|
2045
|
+
*/
|
|
2046
|
+
_extractObjectTypeMembers(e, t) {
|
|
2047
|
+
const r = [];
|
|
2048
|
+
let n;
|
|
2049
|
+
for (let s = 0; s < e.childCount; s += 1) {
|
|
2050
|
+
const o = e.child(s);
|
|
2051
|
+
if (!o) continue;
|
|
2052
|
+
if (o.type === "comment") {
|
|
2053
|
+
const l = t.slice(o.startIndex, o.endIndex);
|
|
2054
|
+
l.startsWith("/**") ? n = {
|
|
2055
|
+
text: l,
|
|
2056
|
+
startLine: o.startPosition.row + 1,
|
|
2057
|
+
startCol: o.startPosition.column + 1
|
|
2058
|
+
} : n = void 0;
|
|
2059
|
+
continue;
|
|
2060
|
+
}
|
|
2061
|
+
if (o.type === "{" || o.type === "}" || o.type === ";" || o.type === ",")
|
|
2062
|
+
continue;
|
|
2063
|
+
let a, c = "", d = null;
|
|
2064
|
+
if (o.type === "method_signature") {
|
|
2065
|
+
const l = o.childForFieldName?.("name") ?? null;
|
|
2066
|
+
l && (c = t.slice(l.startIndex, l.endIndex)), a = "interface-method", d = o;
|
|
2067
|
+
} else if (o.type === "property_signature") {
|
|
2068
|
+
const l = o.childForFieldName?.("name") ?? null;
|
|
2069
|
+
l && (c = t.slice(l.startIndex, l.endIndex)), this._isFunctionTypedProperty(o) ? (a = "interface-method", d = this._findFunctionTypeNode(o)) : a = "interface-property";
|
|
2070
|
+
}
|
|
2071
|
+
if (a && c) {
|
|
2072
|
+
const l = d ? this._extractSignature(d, t) : { params: [], returnType: "void" }, u = n ? {
|
|
2073
|
+
text: n.text,
|
|
2074
|
+
startLine: n.startLine,
|
|
2075
|
+
startCol: n.startCol
|
|
2076
|
+
} : void 0;
|
|
2077
|
+
r.push({ name: c, kind: a, contract: u, signature: l }), n = void 0;
|
|
2078
|
+
} else
|
|
2079
|
+
n = void 0;
|
|
2080
|
+
}
|
|
2081
|
+
return r;
|
|
2082
|
+
}
|
|
2083
|
+
/**
|
|
2084
|
+
* @purpose Checks if a property_signature node has a function type annotation.
|
|
2085
|
+
* @param node property_signature node.
|
|
2086
|
+
* @returns True if the property is function-typed.
|
|
2087
|
+
*/
|
|
2088
|
+
_isFunctionTypedProperty(e) {
|
|
2089
|
+
for (let t = 0; t < e.childCount; t += 1) {
|
|
2090
|
+
const r = e.child(t);
|
|
2091
|
+
if (r?.type === "type_annotation") {
|
|
2092
|
+
for (let n = 0; n < r.childCount; n += 1)
|
|
2093
|
+
if (r.child(n)?.type === "function_type") return !0;
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
return !1;
|
|
2097
|
+
}
|
|
2098
|
+
/**
|
|
2099
|
+
* @purpose Finds the function_type node inside a property_signature's type annotation.
|
|
2100
|
+
* @param node property_signature node.
|
|
2101
|
+
* @returns The function_type node, or null.
|
|
2102
|
+
*/
|
|
2103
|
+
_findFunctionTypeNode(e) {
|
|
2104
|
+
for (let t = 0; t < e.childCount; t += 1) {
|
|
2105
|
+
const r = e.child(t);
|
|
2106
|
+
if (r?.type === "type_annotation")
|
|
2107
|
+
for (let n = 0; n < r.childCount; n += 1) {
|
|
2108
|
+
const s = r.child(n);
|
|
2109
|
+
if (s?.type === "function_type") return s;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
return null;
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* @purpose Extracts members from an enum declaration body.
|
|
2116
|
+
* @param node enum_declaration node.
|
|
2117
|
+
* @param source Full source text.
|
|
2118
|
+
* @returns Array of enum members.
|
|
2119
|
+
*/
|
|
2120
|
+
_extractEnumMembers(e, t) {
|
|
2121
|
+
const r = e.childForFieldName?.("body") ?? null;
|
|
2122
|
+
if (!r) return [];
|
|
2123
|
+
const n = [];
|
|
2124
|
+
let s;
|
|
2125
|
+
for (let o = 0; o < r.childCount; o += 1) {
|
|
2126
|
+
const a = r.child(o);
|
|
2127
|
+
if (a) {
|
|
2128
|
+
if (a.type === "comment") {
|
|
2129
|
+
const c = t.slice(a.startIndex, a.endIndex);
|
|
2130
|
+
c.startsWith("/**") ? s = {
|
|
2131
|
+
text: c,
|
|
2132
|
+
startLine: a.startPosition.row + 1,
|
|
2133
|
+
startCol: a.startPosition.column + 1
|
|
2134
|
+
} : s = void 0;
|
|
2135
|
+
continue;
|
|
2136
|
+
}
|
|
2137
|
+
if (a.type === "property_identifier" || a.type === "identifier" || a.type === "string" || a.type === "shorthand_property_identifier") {
|
|
2138
|
+
const c = t.slice(a.startIndex, a.endIndex), d = s ? {
|
|
2139
|
+
text: s.text,
|
|
2140
|
+
startLine: s.startLine,
|
|
2141
|
+
startCol: s.startCol
|
|
2142
|
+
} : void 0;
|
|
2143
|
+
n.push({
|
|
2144
|
+
name: c,
|
|
2145
|
+
kind: "enum-member",
|
|
2146
|
+
contract: d,
|
|
2147
|
+
signature: { params: [], returnType: "void" }
|
|
2148
|
+
}), s = void 0;
|
|
2149
|
+
} else a.type !== "," && a.type !== "{" && a.type !== "}" && (s = void 0);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
return n;
|
|
2153
|
+
}
|
|
2154
|
+
/**
|
|
2155
|
+
* @purpose Extracts the method name from a method_definition node,
|
|
2156
|
+
* handling getter/setter property names.
|
|
2157
|
+
* @param node method_definition node.
|
|
2158
|
+
* @param source Full source text.
|
|
2159
|
+
* @returns Method name.
|
|
2160
|
+
*/
|
|
2161
|
+
_extractMethodName(e, t) {
|
|
2162
|
+
const r = e.childForFieldName?.("name") ?? null;
|
|
2163
|
+
return r ? t.slice(r.startIndex, r.endIndex) : "unknown";
|
|
2164
|
+
}
|
|
2165
|
+
/**
|
|
2166
|
+
* @purpose Extracts the field name from a public_field_definition node.
|
|
2167
|
+
* @param node public_field_definition node.
|
|
2168
|
+
* @param source Full source text.
|
|
2169
|
+
* @returns Field name.
|
|
2170
|
+
*/
|
|
2171
|
+
_extractFieldName(e, t) {
|
|
2172
|
+
for (let r = 0; r < e.childCount; r += 1) {
|
|
2173
|
+
const n = e.child(r);
|
|
2174
|
+
if (n && (n.type === "property_identifier" || n.type === "identifier"))
|
|
2175
|
+
return t.slice(n.startIndex, n.endIndex);
|
|
2176
|
+
}
|
|
2177
|
+
return "unknown";
|
|
2178
|
+
}
|
|
2179
|
+
/**
|
|
2180
|
+
* @purpose Checks if a method_definition has a given token (e.g., 'get', 'set').
|
|
2181
|
+
* @param node method_definition node.
|
|
2182
|
+
* @param token Token text to search for.
|
|
2183
|
+
* @returns True if the token is present.
|
|
2184
|
+
*/
|
|
2185
|
+
_hasToken(e, t) {
|
|
2186
|
+
for (let r = 0; r < e.childCount; r += 1) {
|
|
2187
|
+
const n = e.child(r);
|
|
2188
|
+
if (n && n.type === t)
|
|
2189
|
+
return !0;
|
|
2190
|
+
}
|
|
2191
|
+
return !1;
|
|
2192
|
+
}
|
|
2193
|
+
/**
|
|
2194
|
+
* @purpose Checks if a class_declaration node has an implements clause.
|
|
2195
|
+
* @param node class_declaration node.
|
|
2196
|
+
* @returns True if the class has `implements` clause.
|
|
2197
|
+
*/
|
|
2198
|
+
_hasImplements(e) {
|
|
2199
|
+
const t = this._findChild(e, "class_heritage");
|
|
2200
|
+
return t ? this._findChild(t, "implements_clause") !== null : !1;
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* @purpose Finds a direct child node by type, returning first match.
|
|
2204
|
+
* @param parent Parent node.
|
|
2205
|
+
* @param type Child node type to find.
|
|
2206
|
+
* @returns First matching child or null.
|
|
2207
|
+
*/
|
|
2208
|
+
_findChild(e, t) {
|
|
2209
|
+
for (let r = 0; r < e.childCount; r += 1) {
|
|
2210
|
+
const n = e.child(r);
|
|
2211
|
+
if (n && n.type === t)
|
|
2212
|
+
return n;
|
|
2213
|
+
}
|
|
2214
|
+
return null;
|
|
2215
|
+
}
|
|
2216
|
+
/**
|
|
2217
|
+
* @purpose Recursively searches for the first ERROR node in the tree.
|
|
2218
|
+
* @param node Root node to search from.
|
|
2219
|
+
* @returns First ERROR node found, or undefined.
|
|
2220
|
+
*/
|
|
2221
|
+
_findErrorNode(e) {
|
|
2222
|
+
if (e.type === "ERROR" || e.isError)
|
|
2223
|
+
return e;
|
|
2224
|
+
for (let t = 0; t < e.childCount; t += 1) {
|
|
2225
|
+
const r = e.child(t);
|
|
2226
|
+
if (!r) continue;
|
|
2227
|
+
const n = this._findErrorNode(r);
|
|
2228
|
+
if (n) return n;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
class Ge extends Error {
|
|
2233
|
+
/** @purpose The conflicting provider key */
|
|
2234
|
+
key;
|
|
2235
|
+
/**
|
|
2236
|
+
* @purpose Construct with the duplicate key and a trace-prefixed message.
|
|
2237
|
+
* @param key The provider key that is already registered.
|
|
2238
|
+
*/
|
|
2239
|
+
constructor(e) {
|
|
2240
|
+
super(`[DuplicateProviderError] Provider with key "${e}" is already registered`), this.name = "DuplicateProviderError", this.key = e;
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
class ze extends Error {
|
|
2244
|
+
/** @purpose The missing provider key */
|
|
2245
|
+
key;
|
|
2246
|
+
/**
|
|
2247
|
+
* @purpose Construct with the missing key and a trace-prefixed message.
|
|
2248
|
+
* @param key The provider key that was not found.
|
|
2249
|
+
*/
|
|
2250
|
+
constructor(e) {
|
|
2251
|
+
super(`[ProviderNotFoundError] Provider with key "${e}" is not registered`), this.name = "ProviderNotFoundError", this.key = e;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
class He {
|
|
2255
|
+
/** @purpose Provider registry — maps string keys to AgentProvider instances | @invariant Empty on construction; mutated only via register/unregister */
|
|
2256
|
+
_providers = /* @__PURE__ */ new Map();
|
|
2257
|
+
/**
|
|
2258
|
+
* @purpose Register a provider under a unique key; rejects on duplicate.
|
|
2259
|
+
* @param key Unique provider identifier — non-empty string.
|
|
2260
|
+
* @param provider The provider instance implementing AgentProvider.
|
|
2261
|
+
* @throws {DuplicateProviderError} When key is already registered.
|
|
2262
|
+
* @post Provider is stored in the registry under the given key.
|
|
2263
|
+
*/
|
|
2264
|
+
register(e, t) {
|
|
2265
|
+
if (this._providers.has(e))
|
|
2266
|
+
throw new Ge(e);
|
|
2267
|
+
this._providers.set(e, t), g.debug(`[AgentMonitor#register] [idle → registered] ${e}`);
|
|
2268
|
+
}
|
|
2269
|
+
/**
|
|
2270
|
+
* @purpose Remove a provider from the registry; no-op when the key is not found.
|
|
2271
|
+
* @param key Provider key to remove.
|
|
2272
|
+
* @post Provider with the given key is no longer in the registry.
|
|
2273
|
+
*/
|
|
2274
|
+
unregister(e) {
|
|
2275
|
+
this._providers.delete(e), g.debug(`[AgentMonitor#unregister] [registered → removed] ${e}`);
|
|
2276
|
+
}
|
|
2277
|
+
/**
|
|
2278
|
+
* @purpose Scan all registered providers concurrently, aggregate and sort results by startedAt descending.
|
|
2279
|
+
* @invariant Graceful degradation: a failing provider returns [] and does not abort other providers.
|
|
2280
|
+
* @param [opts] Optional scan filtering (since timestamp or 'today').
|
|
2281
|
+
* @returns All sessions from all providers, sorted by startedAt descending.
|
|
2282
|
+
* @sideEffect Calls each registered provider's scan() — may read filesystem / database.
|
|
2283
|
+
*/
|
|
2284
|
+
async scanAll(e) {
|
|
2285
|
+
g.debug(`[AgentMonitor#scanAll] [idle → scanning] providers=${this._providers.size}`);
|
|
2286
|
+
const r = (await Promise.all(
|
|
2287
|
+
[...this._providers.values()].map(async (n) => {
|
|
2288
|
+
try {
|
|
2289
|
+
return await n.scan(e);
|
|
2290
|
+
} catch (s) {
|
|
2291
|
+
const o = new Error(`[AgentMonitor#scanAll] Provider ${n.key} failed`, {
|
|
2292
|
+
cause: s
|
|
2293
|
+
});
|
|
2294
|
+
return g.error(`[AgentMonitor#scanAll] [scanning → degraded] ${n.key}`, { error: o }), [];
|
|
2295
|
+
}
|
|
2296
|
+
})
|
|
2297
|
+
)).flat().sort((n, s) => s.startedAt - n.startedAt);
|
|
2298
|
+
return g.info(`[AgentMonitor#scanAll] [scanning → completed] sessions=${r.length}`), r;
|
|
2299
|
+
}
|
|
2300
|
+
/**
|
|
2301
|
+
* @purpose Scan a single provider by key; rejects when the provider is not registered.
|
|
2302
|
+
* @param key Registered provider key.
|
|
2303
|
+
* @param [opts] Optional scan filtering.
|
|
2304
|
+
* @throws {ProviderNotFoundError} When the key is not registered.
|
|
2305
|
+
* @returns Sessions from the specified provider.
|
|
2306
|
+
* @sideEffect Calls the provider's scan() — may read filesystem / database.
|
|
2307
|
+
*/
|
|
2308
|
+
async scanOne(e, t) {
|
|
2309
|
+
g.debug(`[AgentMonitor#scanOne] [idle → scanning] ${e}`);
|
|
2310
|
+
const r = this._providers.get(e);
|
|
2311
|
+
if (r === void 0)
|
|
2312
|
+
throw new ze(e);
|
|
2313
|
+
try {
|
|
2314
|
+
const n = await r.scan(t);
|
|
2315
|
+
return g.info(
|
|
2316
|
+
`[AgentMonitor#scanOne] [scanning → completed] ${e} sessions=${n.length}`
|
|
2317
|
+
), n;
|
|
2318
|
+
} catch (n) {
|
|
2319
|
+
const s = new Error(`[AgentMonitor#scanOne] Provider ${e} failed`, { cause: n });
|
|
2320
|
+
throw g.error(`[AgentMonitor#scanOne] [scanning → failed] ${e}`, { error: s }), s;
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
function Ct() {
|
|
2325
|
+
return new He();
|
|
2326
|
+
}
|
|
2327
|
+
function qe(i) {
|
|
2328
|
+
if (i.length === 0) return /* @__PURE__ */ new Map();
|
|
2329
|
+
try {
|
|
2330
|
+
const e = i.join(","), t = le(`ps -p ${e} -o pid=,pcpu=,rss=,args=`, {
|
|
2331
|
+
encoding: "utf-8",
|
|
2332
|
+
timeout: 5e3
|
|
2333
|
+
}), r = /* @__PURE__ */ new Map(), n = t.trim().split(`
|
|
2334
|
+
`);
|
|
2335
|
+
for (const s of n) {
|
|
2336
|
+
const o = s.trim();
|
|
2337
|
+
if (!o) continue;
|
|
2338
|
+
const a = o.split(/\s+/);
|
|
2339
|
+
if (a.length < 4) continue;
|
|
2340
|
+
const c = a[0], d = a[1], l = a[2], u = a.slice(3).join(" "), h = parseInt(c, 10), p = parseFloat(d), f = parseInt(l, 10);
|
|
2341
|
+
isNaN(h) || isNaN(p) || isNaN(f) || r.set(h, {
|
|
2342
|
+
pid: h,
|
|
2343
|
+
cpuPercent: p,
|
|
2344
|
+
memoryMb: f / 1024,
|
|
2345
|
+
args: u
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2348
|
+
return g.debug(
|
|
2349
|
+
`[psInfo] [idle → completed] Batch ps: ${i.length} requested, ${r.size} alive`
|
|
2350
|
+
), r;
|
|
2351
|
+
} catch (e) {
|
|
2352
|
+
const t = new Error("[psInfo] Batch ps command failed", { cause: e });
|
|
2353
|
+
return g.warn(`[psInfo] [idle → failed] ps command error for ${i.length} PIDs`, { error: t }), /* @__PURE__ */ new Map();
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
function Ve(i) {
|
|
2357
|
+
const e = {}, t = i.match(/--model\s+(\S+)/);
|
|
2358
|
+
t && (e.model = t[1]);
|
|
2359
|
+
const r = i.match(/--effort\s+(\S+)/);
|
|
2360
|
+
return r && (e.effort = r[1]), e;
|
|
2361
|
+
}
|
|
2362
|
+
function Ke(i) {
|
|
2363
|
+
const e = i.match(/--allowedTools\s+([^"]*?)(?:\s+--|$)/);
|
|
2364
|
+
if (!e) return;
|
|
2365
|
+
const r = (e[1] ?? "").match(/Read\(\/\/(?:Users\/[^/]+\/Developer\/)?([^)]+?)(?:\/\*\*)?\)/);
|
|
2366
|
+
if (!r) return;
|
|
2367
|
+
const s = (r[1] ?? "").split("/");
|
|
2368
|
+
return s.length <= 1 ? s[0] : s.slice(-2).join("/");
|
|
2369
|
+
}
|
|
2370
|
+
function Xe(i) {
|
|
2371
|
+
try {
|
|
2372
|
+
const e = A(i, "utf-8"), t = JSON.parse(e);
|
|
2373
|
+
if (typeof t != "object" || t === null)
|
|
2374
|
+
return g.warn(`[readSessionJson] [idle -> skipped] Not a JSON object: ${i}`), null;
|
|
2375
|
+
const r = t;
|
|
2376
|
+
return typeof r.pid != "number" || typeof r.sessionId != "string" || typeof r.cwd != "string" || typeof r.startedAt != "number" ? (g.warn(`[readSessionJson] [idle -> skipped] Missing required fields in: ${i}`), null) : {
|
|
2377
|
+
pid: r.pid,
|
|
2378
|
+
sessionId: r.sessionId,
|
|
2379
|
+
cwd: r.cwd,
|
|
2380
|
+
startedAt: r.startedAt
|
|
2381
|
+
};
|
|
2382
|
+
} catch (e) {
|
|
2383
|
+
const t = new Error("[readSessionJson] Failed to parse session JSON", { cause: e });
|
|
2384
|
+
return g.error(`[readSessionJson] [idle -> failed] ${i}`, { error: t }), null;
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
function Ye(i, e) {
|
|
2388
|
+
try {
|
|
2389
|
+
const t = i.replace(/\//g, "-"), r = $(D(), ".claude", "projects", t, `${e}.jsonl`);
|
|
2390
|
+
if (!K(r))
|
|
2391
|
+
return g.debug(`[readSessionTitle] [idle -> not-found] JSONL missing: ${r}`), "Unknown";
|
|
2392
|
+
const s = A(r, "utf-8").trim().split(`
|
|
2393
|
+
`);
|
|
2394
|
+
for (const o of s)
|
|
2395
|
+
try {
|
|
2396
|
+
const a = JSON.parse(o);
|
|
2397
|
+
if (typeof a == "object" && a !== null && a.type === "ai-title" && typeof a.aiTitle == "string")
|
|
2398
|
+
return a.aiTitle;
|
|
2399
|
+
} catch {
|
|
2400
|
+
}
|
|
2401
|
+
for (const o of s)
|
|
2402
|
+
try {
|
|
2403
|
+
const a = JSON.parse(o);
|
|
2404
|
+
if (typeof a == "object" && a !== null && a.type === "user") {
|
|
2405
|
+
const c = a.message;
|
|
2406
|
+
if (c?.content) {
|
|
2407
|
+
const d = typeof c.content == "string" ? c.content : Array.isArray(c.content) && c.content.length > 0 ? String(c.content[0]?.text ?? "") : "";
|
|
2408
|
+
if (d) return d;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
} catch {
|
|
2412
|
+
}
|
|
2413
|
+
return "Unknown";
|
|
2414
|
+
} catch (t) {
|
|
2415
|
+
const r = new Error("[readSessionTitle] Failed to read session title", { cause: t });
|
|
2416
|
+
return g.warn(`[readSessionTitle] [idle -> failed] cwd=${i} sessionId=${e}`, { error: r }), "Unknown";
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
function Qe(i) {
|
|
2420
|
+
try {
|
|
2421
|
+
const e = $(D(), ".claude", "tasks", i);
|
|
2422
|
+
if (!K(e)) return "Unknown";
|
|
2423
|
+
const t = X(e).filter((n) => n.endsWith(".json"));
|
|
2424
|
+
if (t.length === 0) return "Unknown";
|
|
2425
|
+
let r;
|
|
2426
|
+
for (const n of t)
|
|
2427
|
+
try {
|
|
2428
|
+
const s = A($(e, n), "utf-8"), o = JSON.parse(s), a = o?.subject;
|
|
2429
|
+
if (!a) continue;
|
|
2430
|
+
if (o?.status === "in_progress") return a;
|
|
2431
|
+
r = r ?? a;
|
|
2432
|
+
} catch {
|
|
2433
|
+
}
|
|
2434
|
+
return r ?? "Unknown";
|
|
2435
|
+
} catch {
|
|
2436
|
+
return "Unknown";
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
class xt {
|
|
2440
|
+
/** @see {AgentProvider#key} in ../../model/agent-provider.type.ts */
|
|
2441
|
+
key = "claude";
|
|
2442
|
+
/** @purpose Batch ps inspection function */
|
|
2443
|
+
_psInfo;
|
|
2444
|
+
/** @purpose Claude CLI argument parser */
|
|
2445
|
+
_parseClaudeArgs;
|
|
2446
|
+
/** @purpose Session JSON file reader */
|
|
2447
|
+
_readSessionJson;
|
|
2448
|
+
/** @purpose Session title extractor from JSONL */
|
|
2449
|
+
_readSessionTitle;
|
|
2450
|
+
/** @purpose Logger instance */
|
|
2451
|
+
_logger;
|
|
2452
|
+
/**
|
|
2453
|
+
* @purpose Construct with optional DI overrides for testing.
|
|
2454
|
+
* @param [deps] Injectable function replacements; omitted deps use real implementations.
|
|
2455
|
+
*/
|
|
2456
|
+
constructor(e) {
|
|
2457
|
+
this._psInfo = e?.psInfo ?? qe, this._parseClaudeArgs = e?.parseClaudeArgs ?? Ve, this._readSessionJson = e?.readSessionJson ?? Xe, this._readSessionTitle = e?.readSessionTitle ?? Ye, this._logger = g;
|
|
2458
|
+
}
|
|
2459
|
+
/**
|
|
2460
|
+
* @param [opts] Optional scan filtering parameters.
|
|
2461
|
+
* @returns Parsed agent sessions array.
|
|
2462
|
+
* @see {AgentProvider#scan} in ../../model/agent-provider.type.ts
|
|
2463
|
+
*/
|
|
2464
|
+
async scan(e) {
|
|
2465
|
+
this._logger.debug("[ClaudeProvider#scan] [idle -> scanning]");
|
|
2466
|
+
const t = $(D(), ".claude", "sessions");
|
|
2467
|
+
let r;
|
|
2468
|
+
try {
|
|
2469
|
+
r = X(t).filter((l) => l.endsWith(".json"));
|
|
2470
|
+
} catch (l) {
|
|
2471
|
+
if (l.code === "ENOENT")
|
|
2472
|
+
return this._logger.warn(
|
|
2473
|
+
`[ClaudeProvider#scan] [scanning -> not-found] Sessions dir missing: ${t}`
|
|
2474
|
+
), [];
|
|
2475
|
+
const u = new Error("[ClaudeProvider#scan] Failed to read sessions directory", { cause: l });
|
|
2476
|
+
return this._logger.error("[ClaudeProvider#scan] [scanning -> failed]", { error: u }), [];
|
|
2477
|
+
}
|
|
2478
|
+
const n = [];
|
|
2479
|
+
for (const l of r) {
|
|
2480
|
+
const u = $(t, l), h = this._readSessionJson(u);
|
|
2481
|
+
if (!h) continue;
|
|
2482
|
+
let p = 0;
|
|
2483
|
+
try {
|
|
2484
|
+
p = ae(u).mtimeMs;
|
|
2485
|
+
} catch {
|
|
2486
|
+
p = h.startedAt;
|
|
2487
|
+
}
|
|
2488
|
+
n.push({
|
|
2489
|
+
pid: h.pid,
|
|
2490
|
+
sessionId: h.sessionId,
|
|
2491
|
+
cwd: h.cwd,
|
|
2492
|
+
startedAt: h.startedAt,
|
|
2493
|
+
lastActivityAt: p
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
if (n.length === 0)
|
|
2497
|
+
return this._logger.debug("[ClaudeProvider#scan] [scanning -> empty] No valid sessions found"), [];
|
|
2498
|
+
const s = n.map((l) => l.pid), o = this._psInfo(s), a = Date.now(), c = [];
|
|
2499
|
+
for (const l of n) {
|
|
2500
|
+
const u = o.get(l.pid), h = u !== void 0;
|
|
2501
|
+
let p, f, m;
|
|
2502
|
+
h ? (u?.cpuPercent ?? 0) > 0 ? p = "active" : (p = "idle", f = l.lastActivityAt > 0 ? (a - l.lastActivityAt) / 1e3 : void 0) : (p = "completed", m = l.lastActivityAt > 0 ? l.lastActivityAt : a);
|
|
2503
|
+
const _ = u ? this._parseClaudeArgs(u.args).model : void 0;
|
|
2504
|
+
let w = this._readSessionTitle(l.cwd, l.sessionId);
|
|
2505
|
+
if (w === "Unknown" && (w = Qe(l.sessionId)), w === "Unknown" && u) {
|
|
2506
|
+
const b = Ke(u.args);
|
|
2507
|
+
b ? w = `${M.basename(l.cwd)}: ${b}` : w = `[${M.basename(l.cwd)}] session`;
|
|
2508
|
+
}
|
|
2509
|
+
const I = (a - l.startedAt) / 1e3;
|
|
2510
|
+
c.push({
|
|
2511
|
+
provider: "claude",
|
|
2512
|
+
pid: l.pid,
|
|
2513
|
+
sessionId: l.sessionId,
|
|
2514
|
+
cwd: l.cwd,
|
|
2515
|
+
project: M.basename(l.cwd),
|
|
2516
|
+
startedAt: l.startedAt,
|
|
2517
|
+
lastActivityAt: l.lastActivityAt > 0 ? l.lastActivityAt : void 0,
|
|
2518
|
+
status: p,
|
|
2519
|
+
title: w,
|
|
2520
|
+
model: _,
|
|
2521
|
+
cpuPercent: u?.cpuPercent,
|
|
2522
|
+
memoryMb: u?.memoryMb,
|
|
2523
|
+
elapsedSeconds: I,
|
|
2524
|
+
idleSeconds: f,
|
|
2525
|
+
completedAt: m
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
let d = c;
|
|
2529
|
+
if (e?.since !== void 0) {
|
|
2530
|
+
const l = e.since === "today" ? Ze() : e.since;
|
|
2531
|
+
d = c.filter((u) => u.startedAt >= l);
|
|
2532
|
+
}
|
|
2533
|
+
return this._logger.info(
|
|
2534
|
+
`[ClaudeProvider#scan] [scanning -> completed] ${d.length} sessions (${c.length} total)`
|
|
2535
|
+
), d;
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
function Ze() {
|
|
2539
|
+
const i = /* @__PURE__ */ new Date();
|
|
2540
|
+
return i.setHours(0, 0, 0, 0), i.getTime();
|
|
2541
|
+
}
|
|
2542
|
+
function et(i, e) {
|
|
2543
|
+
let t = "SELECT id, slug, title, directory, time_created, time_updated, agent, model, tokens_input, tokens_output, parent_id FROM session WHERE time_archived IS NULL";
|
|
2544
|
+
const r = [], n = e?.since ?? Date.now() - 864e5;
|
|
2545
|
+
return t += " AND time_updated >= ?", r.push(n), t += " ORDER BY time_updated DESC", i.prepare(t).all(...r);
|
|
2546
|
+
}
|
|
2547
|
+
function tt(i, e) {
|
|
2548
|
+
const r = i.prepare(
|
|
2549
|
+
"SELECT data FROM message WHERE session_id = ? ORDER BY time_updated DESC LIMIT 1"
|
|
2550
|
+
).get(e);
|
|
2551
|
+
if (!r?.data) return null;
|
|
2552
|
+
try {
|
|
2553
|
+
const s = JSON.parse(r.data)?.content;
|
|
2554
|
+
return typeof s == "string" ? s : Array.isArray(s) && s.length > 0 && typeof s[0]?.text == "string" ? s[0].text : null;
|
|
2555
|
+
} catch {
|
|
2556
|
+
return r.data;
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
function nt(i) {
|
|
2560
|
+
if (i)
|
|
2561
|
+
try {
|
|
2562
|
+
return JSON.parse(i).id ?? i;
|
|
2563
|
+
} catch {
|
|
2564
|
+
return "unknown";
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
const rt = "~/.local/share/opencode/opencode.db";
|
|
2568
|
+
class Rt {
|
|
2569
|
+
/** @purpose Provider identifier key ('opencode') */
|
|
2570
|
+
key = "opencode";
|
|
2571
|
+
/** @purpose Resolved dependency injection container */
|
|
2572
|
+
_deps;
|
|
2573
|
+
/** @purpose Logger instance for structured logging */
|
|
2574
|
+
_logger = g;
|
|
2575
|
+
/**
|
|
2576
|
+
* @purpose Construct with optional dependency injection for testing.
|
|
2577
|
+
* @param [deps] Injectable query functions and db path overrides.
|
|
2578
|
+
*/
|
|
2579
|
+
constructor(e) {
|
|
2580
|
+
this._deps = {
|
|
2581
|
+
dbPath: e?.dbPath ?? rt,
|
|
2582
|
+
querySessions: e?.querySessions ?? et,
|
|
2583
|
+
queryLastMessage: e?.queryLastMessage ?? tt,
|
|
2584
|
+
parseModelJson: e?.parseModelJson ?? nt
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
/**
|
|
2588
|
+
* @param [opts] Optional scan filtering parameters.
|
|
2589
|
+
* @returns Parsed agent sessions array.
|
|
2590
|
+
* @see {AgentProvider#scan} in ../../model/agent-provider.type.ts
|
|
2591
|
+
*/
|
|
2592
|
+
async scan(e) {
|
|
2593
|
+
this._logger.debug("[OpenCodeProvider#scan] [idle → opening]");
|
|
2594
|
+
const t = this._resolvePath(this._deps.dbPath);
|
|
2595
|
+
let r;
|
|
2596
|
+
try {
|
|
2597
|
+
r = new de(t);
|
|
2598
|
+
} catch (n) {
|
|
2599
|
+
return this._logger.warn(`[OpenCodeProvider#scan] [opening → not_found] ${t}`, { cause: n }), [];
|
|
2600
|
+
}
|
|
2601
|
+
try {
|
|
2602
|
+
const n = this._deps.querySessions(r, e), s = [];
|
|
2603
|
+
for (const o of n) {
|
|
2604
|
+
const a = this._deps.parseModelJson(o.model), c = this._deps.queryLastMessage(r, o.id), d = o.time_created, l = o.time_updated, u = Math.max(0, Math.round((l - d) / 1e3));
|
|
2605
|
+
s.push({
|
|
2606
|
+
provider: "opencode",
|
|
2607
|
+
pid: null,
|
|
2608
|
+
sessionId: o.slug,
|
|
2609
|
+
parentId: o.parent_id ?? void 0,
|
|
2610
|
+
title: o.title ?? "",
|
|
2611
|
+
slug: o.slug,
|
|
2612
|
+
cwd: o.directory,
|
|
2613
|
+
project: o.directory.split("/").pop() ?? o.directory,
|
|
2614
|
+
model: a,
|
|
2615
|
+
agent: o.agent ?? void 0,
|
|
2616
|
+
status: "active",
|
|
2617
|
+
startedAt: d,
|
|
2618
|
+
lastActivityAt: l,
|
|
2619
|
+
elapsedSeconds: u,
|
|
2620
|
+
tokensInput: o.tokens_input ?? void 0,
|
|
2621
|
+
tokensOutput: o.tokens_output ?? void 0,
|
|
2622
|
+
lastMessage: c ?? void 0
|
|
2623
|
+
});
|
|
2624
|
+
}
|
|
2625
|
+
return this._logger.info(`[OpenCodeProvider#scan] [querying → scanned] ${s.length} sessions`), s;
|
|
2626
|
+
} catch (n) {
|
|
2627
|
+
const s = new Error("[OpenCodeProvider#scan] Query failed", { cause: n });
|
|
2628
|
+
return this._logger.error("[OpenCodeProvider#scan] [querying → failed]", { error: s }), [];
|
|
2629
|
+
} finally {
|
|
2630
|
+
r.close();
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
/**
|
|
2634
|
+
* @purpose Resolve tilde-prefixed path to an absolute path using the home directory.
|
|
2635
|
+
* @param raw Raw path string potentially starting with ~/.
|
|
2636
|
+
* @returns Absolute path with ~ resolved to os.homedir().
|
|
2637
|
+
*/
|
|
2638
|
+
_resolvePath(e) {
|
|
2639
|
+
return e.startsWith("~/") ? M.join(ce.homedir(), e.slice(2)) : e;
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
const st = [
|
|
2643
|
+
"status",
|
|
2644
|
+
"title",
|
|
2645
|
+
"lastActivityAt",
|
|
2646
|
+
"elapsedSeconds",
|
|
2647
|
+
"idleSeconds",
|
|
2648
|
+
"toolCallCount",
|
|
2649
|
+
"errorCount",
|
|
2650
|
+
"lastMessage",
|
|
2651
|
+
"tokensInput",
|
|
2652
|
+
"tokensOutput"
|
|
2653
|
+
];
|
|
2654
|
+
function it(i, e) {
|
|
2655
|
+
g.debug("[diff] [idle → comparing]");
|
|
2656
|
+
const t = new Map(i.map((a) => [a.sessionId, a])), r = new Map(e.map((a) => [a.sessionId, a])), n = [], s = [], o = [];
|
|
2657
|
+
for (const a of e) {
|
|
2658
|
+
const c = t.get(a.sessionId);
|
|
2659
|
+
if (!c) {
|
|
2660
|
+
n.push(a);
|
|
2661
|
+
continue;
|
|
2662
|
+
}
|
|
2663
|
+
ot(c, a) && o.push(a);
|
|
2664
|
+
}
|
|
2665
|
+
for (const a of i)
|
|
2666
|
+
r.has(a.sessionId) || s.push(a);
|
|
2667
|
+
return g.debug(
|
|
2668
|
+
`[diff] [comparing → done] added=${n.length} removed=${s.length} updated=${o.length}`
|
|
2669
|
+
), { added: n, removed: s, updated: o };
|
|
2670
|
+
}
|
|
2671
|
+
function ot(i, e) {
|
|
2672
|
+
return st.some((t) => i[t] !== e[t]);
|
|
2673
|
+
}
|
|
2674
|
+
const at = 3e5, q = 100;
|
|
2675
|
+
function V(i, e) {
|
|
2676
|
+
const t = Date.now();
|
|
2677
|
+
for (const r of i)
|
|
2678
|
+
r.lastActivityAt !== void 0 && t - r.lastActivityAt > e && (r.status = "idle");
|
|
2679
|
+
}
|
|
2680
|
+
function ct() {
|
|
2681
|
+
return { added: [], removed: [], updated: [] };
|
|
2682
|
+
}
|
|
2683
|
+
async function* Et(i, e) {
|
|
2684
|
+
if (e.interval < q)
|
|
2685
|
+
throw new RangeError(
|
|
2686
|
+
`[observe] interval must be >= ${q}ms, got ${e.interval}`
|
|
2687
|
+
);
|
|
2688
|
+
const t = e.idleThresholdMs ?? at;
|
|
2689
|
+
g.debug(
|
|
2690
|
+
`[observe] [idle → initializing] interval=${e.interval}ms idleThreshold=${t}ms`
|
|
2691
|
+
);
|
|
2692
|
+
let r;
|
|
2693
|
+
try {
|
|
2694
|
+
r = await i.scanAll(), V(r, t), g.debug(`[observe] [initializing → baseline] sessions=${r.length}`);
|
|
2695
|
+
} catch (n) {
|
|
2696
|
+
const s = new Error("[observe] Baseline scan failed", { cause: n });
|
|
2697
|
+
g.error("[observe] [initializing → failed]", { error: s }), r = [];
|
|
2698
|
+
}
|
|
2699
|
+
for (; ; ) {
|
|
2700
|
+
await new Promise((n) => setTimeout(n, e.interval));
|
|
2701
|
+
try {
|
|
2702
|
+
const n = await i.scanAll();
|
|
2703
|
+
V(n, t);
|
|
2704
|
+
const s = it(r, n);
|
|
2705
|
+
g.debug(
|
|
2706
|
+
`[observe] [polling → yielded] added=${s.added.length} removed=${s.removed.length} updated=${s.updated.length}`
|
|
2707
|
+
), r = n, yield s;
|
|
2708
|
+
} catch (n) {
|
|
2709
|
+
const s = new Error("[observe] Scan cycle failed", { cause: n });
|
|
2710
|
+
g.error("[observe] [polling → degraded]", { error: s }), yield ct();
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
export {
|
|
2715
|
+
xt as C,
|
|
2716
|
+
It as D,
|
|
2717
|
+
Rt as O,
|
|
2718
|
+
$e as R,
|
|
2719
|
+
_t as V,
|
|
2720
|
+
vt as a,
|
|
2721
|
+
St as b,
|
|
2722
|
+
wt as c,
|
|
2723
|
+
yt as d,
|
|
2724
|
+
Ct as e,
|
|
2725
|
+
be as f,
|
|
2726
|
+
L as g,
|
|
2727
|
+
g as l,
|
|
2728
|
+
Et as o,
|
|
2729
|
+
gt as p,
|
|
2730
|
+
bt as r,
|
|
2731
|
+
mt as s
|
|
2732
|
+
};
|