@linghun/pre-engine-win32-x64 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/bundled/pre-engine/win32-x64/cpp-deep-layer.cjs +132 -0
- package/bundled/pre-engine/win32-x64/csharp-deep-layer.cjs +200 -0
- package/bundled/pre-engine/win32-x64/dart-deep-layer.cjs +106 -0
- package/bundled/pre-engine/win32-x64/go-deep-layer.cjs +427 -0
- package/bundled/pre-engine/win32-x64/java-deep-layer.cjs +350 -0
- package/bundled/pre-engine/win32-x64/kotlin-deep-layer.cjs +105 -0
- package/bundled/pre-engine/win32-x64/linghun-pre-engine.exe +0 -0
- package/bundled/pre-engine/win32-x64/php-deep-layer.cjs +179 -0
- package/bundled/pre-engine/win32-x64/py-deep-layer.cjs +142 -0
- package/bundled/pre-engine/win32-x64/ruby-deep-layer.cjs +98 -0
- package/bundled/pre-engine/win32-x64/rust-deep-layer.cjs +481 -0
- package/bundled/pre-engine/win32-x64/shell-deep-layer.cjs +161 -0
- package/bundled/pre-engine/win32-x64/sql-deep-layer.cjs +172 -0
- package/bundled/pre-engine/win32-x64/swift-deep-layer.cjs +113 -0
- package/bundled/pre-engine/win32-x64/ts-deep-layer.cjs +142 -0
- package/package.json +17 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const readline = require("readline");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const { spawn, spawnSync } = require("child_process");
|
|
6
|
+
|
|
7
|
+
// --- LSP JSON-RPC framing ---
|
|
8
|
+
let msgId = 0;
|
|
9
|
+
function lspEncode(obj) {
|
|
10
|
+
const body = JSON.stringify(obj);
|
|
11
|
+
return `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`;
|
|
12
|
+
}
|
|
13
|
+
function lspRequest(method, params) {
|
|
14
|
+
return lspEncode({ jsonrpc: "2.0", id: ++msgId, method, params });
|
|
15
|
+
}
|
|
16
|
+
function lspNotify(method, params) {
|
|
17
|
+
return lspEncode({ jsonrpc: "2.0", method, params });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function fileUri(p) {
|
|
21
|
+
const abs = path.resolve(p).replace(/\\/g, "/");
|
|
22
|
+
return abs.startsWith("/") ? `file://${abs}` : `file:///${abs}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normUri(uri) { return uri.toLowerCase(); }
|
|
26
|
+
|
|
27
|
+
// --- jdtls session state ---
|
|
28
|
+
let jdtlsProc = null;
|
|
29
|
+
let jdtlsRoot = null;
|
|
30
|
+
let jdtlsReady = false;
|
|
31
|
+
let jdtlsBuffer = "";
|
|
32
|
+
let jdtlsExpectedLen = -1;
|
|
33
|
+
let jdtlsInitResolve = null;
|
|
34
|
+
let jdtlsInitReject = null;
|
|
35
|
+
let jdtlsDiagnostics = new Map();
|
|
36
|
+
let jdtlsDiagTimers = new Map();
|
|
37
|
+
let jdtlsDiagWaiters = [];
|
|
38
|
+
let openDocs = new Map();
|
|
39
|
+
let jdtlsDocOpenTime = new Map();
|
|
40
|
+
let jdtlsEmptyCount = new Map();
|
|
41
|
+
let jdtlsWarmedUp = false;
|
|
42
|
+
let jdtlsStartingPromise = null;
|
|
43
|
+
|
|
44
|
+
const BOOTSTRAP_BUDGET_MS = 5000;
|
|
45
|
+
const WARM_SETTLE_MS = 2000;
|
|
46
|
+
|
|
47
|
+
function findJdtls() {
|
|
48
|
+
const whichCmd = process.platform === "win32" ? "where.exe" : "which";
|
|
49
|
+
const r = spawnSync(whichCmd, ["jdtls"], {
|
|
50
|
+
encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], windowsHide: true,
|
|
51
|
+
});
|
|
52
|
+
if (r.status === 0) {
|
|
53
|
+
const line = r.stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean)[0];
|
|
54
|
+
return line || null;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function killJdtls() {
|
|
60
|
+
if (jdtlsProc) { try { jdtlsProc.kill(); } catch {} jdtlsProc = null; }
|
|
61
|
+
jdtlsReady = false;
|
|
62
|
+
jdtlsWarmedUp = false;
|
|
63
|
+
jdtlsStartingPromise = null;
|
|
64
|
+
jdtlsInitResolve = null;
|
|
65
|
+
jdtlsInitReject = null;
|
|
66
|
+
jdtlsDiagWaiters.forEach(w => w.resolve([]));
|
|
67
|
+
jdtlsDiagWaiters = [];
|
|
68
|
+
jdtlsDiagTimers.forEach(t => clearTimeout(t));
|
|
69
|
+
jdtlsDiagTimers.clear();
|
|
70
|
+
openDocs.clear();
|
|
71
|
+
jdtlsDocOpenTime.clear();
|
|
72
|
+
jdtlsEmptyCount.clear();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function onJdtlsData(chunk) {
|
|
76
|
+
jdtlsBuffer += chunk.toString();
|
|
77
|
+
while (true) {
|
|
78
|
+
if (jdtlsExpectedLen === -1) {
|
|
79
|
+
const headerEnd = jdtlsBuffer.indexOf("\r\n\r\n");
|
|
80
|
+
if (headerEnd === -1) break;
|
|
81
|
+
const header = jdtlsBuffer.slice(0, headerEnd);
|
|
82
|
+
const m = header.match(/Content-Length:\s*(\d+)/i);
|
|
83
|
+
if (!m) { jdtlsBuffer = jdtlsBuffer.slice(headerEnd + 4); continue; }
|
|
84
|
+
jdtlsExpectedLen = parseInt(m[1], 10);
|
|
85
|
+
jdtlsBuffer = jdtlsBuffer.slice(headerEnd + 4);
|
|
86
|
+
}
|
|
87
|
+
if (jdtlsBuffer.length < jdtlsExpectedLen) break;
|
|
88
|
+
const body = jdtlsBuffer.slice(0, jdtlsExpectedLen);
|
|
89
|
+
jdtlsBuffer = jdtlsBuffer.slice(jdtlsExpectedLen);
|
|
90
|
+
jdtlsExpectedLen = -1;
|
|
91
|
+
handleJdtlsMessage(body);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function handleJdtlsMessage(raw) {
|
|
96
|
+
let msg;
|
|
97
|
+
try { msg = JSON.parse(raw); } catch { return; }
|
|
98
|
+
|
|
99
|
+
if (msg.id && jdtlsInitResolve && !jdtlsReady) {
|
|
100
|
+
jdtlsReady = true;
|
|
101
|
+
jdtlsProc.stdin.write(lspNotify("initialized", {}));
|
|
102
|
+
jdtlsInitResolve();
|
|
103
|
+
jdtlsInitResolve = null;
|
|
104
|
+
jdtlsInitReject = null;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (msg.method === "textDocument/publishDiagnostics") {
|
|
109
|
+
const uri = normUri(msg.params.uri);
|
|
110
|
+
const diags = msg.params.diagnostics || [];
|
|
111
|
+
jdtlsDiagnostics.set(uri, diags);
|
|
112
|
+
|
|
113
|
+
if (diags.length > 0) {
|
|
114
|
+
jdtlsEmptyCount.set(uri, 0);
|
|
115
|
+
for (const w of jdtlsDiagWaiters.filter(x => x.uri === uri)) {
|
|
116
|
+
clearTimeout(w.timer);
|
|
117
|
+
w.resolve(diags);
|
|
118
|
+
}
|
|
119
|
+
jdtlsDiagWaiters = jdtlsDiagWaiters.filter(x => x.uri !== uri);
|
|
120
|
+
} else {
|
|
121
|
+
const count = (jdtlsEmptyCount.get(uri) || 0) + 1;
|
|
122
|
+
jdtlsEmptyCount.set(uri, count);
|
|
123
|
+
if (jdtlsWarmedUp && count >= 2) {
|
|
124
|
+
for (const w of jdtlsDiagWaiters.filter(x => x.uri === uri)) {
|
|
125
|
+
clearTimeout(w.timer);
|
|
126
|
+
w.resolve([]);
|
|
127
|
+
}
|
|
128
|
+
jdtlsDiagWaiters = jdtlsDiagWaiters.filter(x => x.uri !== uri);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function uriToRel(uri, root) {
|
|
135
|
+
let p = uri.replace(/^file:\/\/\/?/, "");
|
|
136
|
+
p = decodeURIComponent(p).replace(/\//g, path.sep);
|
|
137
|
+
if (process.platform === "win32" && /^[a-zA-Z]:/.test(p)) {
|
|
138
|
+
// already absolute
|
|
139
|
+
} else if (!path.isAbsolute(p)) {
|
|
140
|
+
p = path.join(root, p);
|
|
141
|
+
}
|
|
142
|
+
return path.relative(root, p).replace(/\\/g, "/");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function waitForDiag(uri, timeoutMs) {
|
|
146
|
+
return new Promise((resolve) => {
|
|
147
|
+
const existing = jdtlsDiagnostics.get(uri);
|
|
148
|
+
if (existing && existing.length > 0) { resolve(existing); return; }
|
|
149
|
+
if (jdtlsWarmedUp && (jdtlsEmptyCount.get(uri) || 0) >= 2) { resolve([]); return; }
|
|
150
|
+
|
|
151
|
+
const timer = setTimeout(() => {
|
|
152
|
+
jdtlsDiagWaiters = jdtlsDiagWaiters.filter(x => x !== entry);
|
|
153
|
+
resolve(jdtlsDiagnostics.get(uri) || []);
|
|
154
|
+
}, timeoutMs);
|
|
155
|
+
const entry = { uri, resolve, timer };
|
|
156
|
+
jdtlsDiagWaiters.push(entry);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function startJdtls(root) {
|
|
161
|
+
if (jdtlsStartingPromise) return jdtlsStartingPromise;
|
|
162
|
+
jdtlsStartingPromise = new Promise((resolve, reject) => {
|
|
163
|
+
const bin = findJdtls();
|
|
164
|
+
if (!bin) { resolve(false); return; }
|
|
165
|
+
jdtlsRoot = root;
|
|
166
|
+
jdtlsInitResolve = () => resolve(true);
|
|
167
|
+
jdtlsInitReject = reject;
|
|
168
|
+
const dataDir = path.join(root, ".jdtls-data-" + process.pid);
|
|
169
|
+
jdtlsProc = spawn(bin, ["-data", dataDir], {
|
|
170
|
+
cwd: root,
|
|
171
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
172
|
+
windowsHide: true,
|
|
173
|
+
});
|
|
174
|
+
jdtlsProc.stdout.on("data", onJdtlsData);
|
|
175
|
+
jdtlsProc.on("error", (e) => { if (jdtlsInitReject) { jdtlsInitReject(e); jdtlsInitReject = null; } });
|
|
176
|
+
jdtlsProc.on("close", () => { jdtlsReady = false; });
|
|
177
|
+
|
|
178
|
+
const initParams = {
|
|
179
|
+
processId: process.pid,
|
|
180
|
+
rootUri: fileUri(root),
|
|
181
|
+
capabilities: { textDocument: { publishDiagnostics: { relatedInformation: true } } },
|
|
182
|
+
workspaceFolders: [{ uri: fileUri(root), name: path.basename(root) }],
|
|
183
|
+
};
|
|
184
|
+
jdtlsProc.stdin.write(lspRequest("initialize", initParams));
|
|
185
|
+
|
|
186
|
+
setTimeout(() => {
|
|
187
|
+
if (!jdtlsReady && jdtlsInitResolve) {
|
|
188
|
+
jdtlsInitResolve = null;
|
|
189
|
+
jdtlsInitReject = null;
|
|
190
|
+
resolve(false);
|
|
191
|
+
}
|
|
192
|
+
}, BOOTSTRAP_BUDGET_MS);
|
|
193
|
+
});
|
|
194
|
+
return jdtlsStartingPromise;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function eagerWarmUp(root, files) {
|
|
198
|
+
let candidates = [];
|
|
199
|
+
if (files && files.length > 0) {
|
|
200
|
+
candidates = files.map(f => path.isAbsolute(f) ? f : path.join(root, f));
|
|
201
|
+
} else {
|
|
202
|
+
try {
|
|
203
|
+
const entries = fs.readdirSync(root);
|
|
204
|
+
candidates = entries
|
|
205
|
+
.filter(e => e.endsWith(".java"))
|
|
206
|
+
.slice(0, 5)
|
|
207
|
+
.map(e => path.join(root, e));
|
|
208
|
+
} catch {}
|
|
209
|
+
if (candidates.length === 0) {
|
|
210
|
+
const srcDir = path.join(root, "src");
|
|
211
|
+
try {
|
|
212
|
+
const entries = fs.readdirSync(srcDir);
|
|
213
|
+
candidates = entries
|
|
214
|
+
.filter(e => e.endsWith(".java"))
|
|
215
|
+
.slice(0, 5)
|
|
216
|
+
.map(e => path.join(srcDir, e));
|
|
217
|
+
} catch {}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (const fp of candidates) {
|
|
221
|
+
if (fs.existsSync(fp)) sendDocOpen(fp);
|
|
222
|
+
}
|
|
223
|
+
setTimeout(() => { jdtlsWarmedUp = true; }, WARM_SETTLE_MS);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function sendDocOpen(filePath) {
|
|
227
|
+
const uri = fileUri(filePath);
|
|
228
|
+
const nuri = normUri(uri);
|
|
229
|
+
let text;
|
|
230
|
+
try { text = fs.readFileSync(filePath, "utf8"); } catch { return; }
|
|
231
|
+
if (openDocs.has(nuri)) {
|
|
232
|
+
const version = (openDocs.get(nuri) || 0) + 1;
|
|
233
|
+
openDocs.set(nuri, version);
|
|
234
|
+
jdtlsEmptyCount.set(nuri, 0);
|
|
235
|
+
jdtlsDiagnostics.delete(nuri);
|
|
236
|
+
jdtlsProc.stdin.write(lspNotify("textDocument/didChange", {
|
|
237
|
+
textDocument: { uri, version },
|
|
238
|
+
contentChanges: [{ text }],
|
|
239
|
+
}));
|
|
240
|
+
} else {
|
|
241
|
+
openDocs.set(nuri, 1);
|
|
242
|
+
jdtlsEmptyCount.set(nuri, 0);
|
|
243
|
+
jdtlsDocOpenTime.set(nuri, Date.now());
|
|
244
|
+
jdtlsProc.stdin.write(lspNotify("textDocument/didOpen", {
|
|
245
|
+
textDocument: { uri, languageId: "java", version: 1, text },
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function queryLsp(root, files) {
|
|
251
|
+
const results = [];
|
|
252
|
+
const promises = files.map(f => {
|
|
253
|
+
const fp = path.isAbsolute(f) ? f : path.join(root, f);
|
|
254
|
+
sendDocOpen(fp);
|
|
255
|
+
const uri = normUri(fileUri(fp));
|
|
256
|
+
return waitForDiag(uri, WARM_SETTLE_MS + 3000).then(diags => {
|
|
257
|
+
for (const d of diags) {
|
|
258
|
+
if (d.severity && d.severity > 1) continue;
|
|
259
|
+
results.push({
|
|
260
|
+
file: path.relative(root, fp).replace(/\\/g, "/"),
|
|
261
|
+
line: (d.range?.start?.line || 0) + 1,
|
|
262
|
+
col: (d.range?.start?.character || 0) + 1,
|
|
263
|
+
message: d.message || "error",
|
|
264
|
+
source: "java-deep-layer",
|
|
265
|
+
kind: "type_error",
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
return Promise.all(promises).then(() => results);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function findJavac() {
|
|
274
|
+
const whichCmd = process.platform === "win32" ? "where.exe" : "which";
|
|
275
|
+
const r = spawnSync(whichCmd, ["javac"], {
|
|
276
|
+
encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], windowsHide: true,
|
|
277
|
+
});
|
|
278
|
+
if (r.status === 0) {
|
|
279
|
+
const line = r.stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean)[0];
|
|
280
|
+
return line || null;
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function runJavac(root, files) {
|
|
286
|
+
const javac = findJavac();
|
|
287
|
+
if (!javac) return null;
|
|
288
|
+
const absPaths = files.map(f => path.isAbsolute(f) ? f : path.join(root, f));
|
|
289
|
+
const outDir = path.join(root, "__javac_out__");
|
|
290
|
+
try { fs.mkdirSync(outDir, { recursive: true }); } catch {}
|
|
291
|
+
const args = ["-J-Duser.language=en", "-J-Duser.country=US", "-d", outDir, "-Xlint:all", ...absPaths];
|
|
292
|
+
const r = spawnSync(javac, args, {
|
|
293
|
+
cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true, timeout: 15000,
|
|
294
|
+
});
|
|
295
|
+
try { fs.rmSync(outDir, { recursive: true, force: true }); } catch {}
|
|
296
|
+
if (r.status === 0) return [];
|
|
297
|
+
const issues = [];
|
|
298
|
+
const re = /^(.+?):(\d+):\s*(?:error|错误):\s*(.+)$/gm;
|
|
299
|
+
const output = (r.stderr || "") + (r.stdout || "");
|
|
300
|
+
let m;
|
|
301
|
+
while ((m = re.exec(output)) !== null) {
|
|
302
|
+
const rel = path.relative(root, m[1]).replace(/\\/g, "/");
|
|
303
|
+
issues.push({ file: rel, line: parseInt(m[2], 10), col: 1, message: m[3], source: "java-deep-layer", kind: "type_error" });
|
|
304
|
+
}
|
|
305
|
+
return issues;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function handleRequest(req) {
|
|
309
|
+
const t0 = Date.now();
|
|
310
|
+
const root = req.root || process.cwd();
|
|
311
|
+
const files = req.files || [];
|
|
312
|
+
if (files.length === 0) {
|
|
313
|
+
return { issues: [], status: "clean", reason: "no_files", elapsed_ms: 0 };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const javacResult = runJavac(root, files);
|
|
317
|
+
if (javacResult !== null && javacResult.length > 0) {
|
|
318
|
+
return { issues: javacResult, status: "type_error", reason: "javac", elapsed_ms: Date.now() - t0, bootstrap: true };
|
|
319
|
+
}
|
|
320
|
+
if (javacResult !== null && javacResult.length === 0) {
|
|
321
|
+
return { issues: [], status: "clean", reason: "javac_clean", elapsed_ms: Date.now() - t0, bootstrap: true };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const lspOk = await startJdtls(root);
|
|
325
|
+
if (lspOk) {
|
|
326
|
+
eagerWarmUp(root, files);
|
|
327
|
+
const lspIssues = await queryLsp(root, files);
|
|
328
|
+
const elapsed = Date.now() - t0;
|
|
329
|
+
if (lspIssues.length > 0) {
|
|
330
|
+
return { issues: lspIssues, status: "type_error", reason: "jdtls", elapsed_ms: elapsed };
|
|
331
|
+
}
|
|
332
|
+
return { issues: [], status: "clean", reason: "jdtls_clean", elapsed_ms: elapsed };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return { issues: [], status: "unavailable", reason: "no_javac_no_jdtls", error: "neither javac nor jdtls found", elapsed_ms: Date.now() - t0 };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
339
|
+
rl.on("line", (line) => {
|
|
340
|
+
const trimmed = line.trim();
|
|
341
|
+
if (!trimmed) return;
|
|
342
|
+
let req;
|
|
343
|
+
try { req = JSON.parse(trimmed); } catch { return; }
|
|
344
|
+
handleRequest(req).then((result) => {
|
|
345
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
346
|
+
}).catch((err) => {
|
|
347
|
+
process.stdout.write(JSON.stringify({ issues: [], status: "error", error: String(err), elapsed_ms: 0 }) + "\n");
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
rl.on("close", () => { killJdtls(); process.exit(0); });
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const readline = require("readline");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { spawnSync } = require("child_process");
|
|
5
|
+
|
|
6
|
+
function findKotlinc() {
|
|
7
|
+
const whichCmd = process.platform === "win32" ? "where.exe" : "which";
|
|
8
|
+
const r = spawnSync(whichCmd, ["kotlinc"], {
|
|
9
|
+
encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], windowsHide: true,
|
|
10
|
+
});
|
|
11
|
+
if (r.status === 0) {
|
|
12
|
+
const line = r.stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean)[0];
|
|
13
|
+
return line || null;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function runKotlincCheck(root, files) {
|
|
19
|
+
const kotlinc = findKotlinc();
|
|
20
|
+
if (!kotlinc) return null;
|
|
21
|
+
|
|
22
|
+
const issues = [];
|
|
23
|
+
for (const f of files) {
|
|
24
|
+
const abs = path.isAbsolute(f) ? f : path.join(root, f);
|
|
25
|
+
let r;
|
|
26
|
+
try {
|
|
27
|
+
r = spawnSync(kotlinc, ["-script", abs, "-nowarn"], {
|
|
28
|
+
cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"],
|
|
29
|
+
windowsHide: true, timeout: 60000,
|
|
30
|
+
});
|
|
31
|
+
} catch (e) { continue; }
|
|
32
|
+
|
|
33
|
+
if (r.error) continue;
|
|
34
|
+
|
|
35
|
+
if (r.status !== 0) {
|
|
36
|
+
const output = (r.stderr || "") + (r.stdout || "");
|
|
37
|
+
const rel = path.relative(root, abs).replace(/\\/g, "/");
|
|
38
|
+
const lines = output.split(/\r?\n/);
|
|
39
|
+
let found = false;
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
const m = line.match(/^.+?:(\d+):(\d+):\s*(error|warning):\s*(.+)$/);
|
|
42
|
+
if (m) {
|
|
43
|
+
issues.push({
|
|
44
|
+
file: rel,
|
|
45
|
+
line: parseInt(m[1], 10),
|
|
46
|
+
col: parseInt(m[2], 10),
|
|
47
|
+
kind: m[3] === "warning" ? "warning" : "error",
|
|
48
|
+
message: m[4].trim(),
|
|
49
|
+
source: "kotlin-deep-layer",
|
|
50
|
+
});
|
|
51
|
+
found = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!found) {
|
|
55
|
+
const firstLine = lines.find(l => l.trim()) || "compilation error";
|
|
56
|
+
issues.push({
|
|
57
|
+
file: rel, line: 1, col: 1,
|
|
58
|
+
kind: "error", message: firstLine.trim(),
|
|
59
|
+
source: "kotlin-deep-layer",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { issues };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function handleRequest(req) {
|
|
68
|
+
const t0 = Date.now();
|
|
69
|
+
const root = req.root || process.cwd();
|
|
70
|
+
const files = req.files || [];
|
|
71
|
+
if (files.length === 0) {
|
|
72
|
+
return { issues: [], status: "clean", reason: "no_files", elapsed_ms: 0 };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const lintResult = runKotlincCheck(root, files);
|
|
76
|
+
if (lintResult) {
|
|
77
|
+
return {
|
|
78
|
+
issues: lintResult.issues,
|
|
79
|
+
status: lintResult.issues.length > 0 ? "kotlin_error" : "clean",
|
|
80
|
+
reason: "kotlinc",
|
|
81
|
+
elapsed_ms: Date.now() - t0,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
issues: [],
|
|
87
|
+
status: "unavailable",
|
|
88
|
+
reason: "kotlinc_not_found",
|
|
89
|
+
elapsed_ms: Date.now() - t0,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
94
|
+
rl.on("line", (line) => {
|
|
95
|
+
const trimmed = line.trim();
|
|
96
|
+
if (!trimmed) return;
|
|
97
|
+
let req;
|
|
98
|
+
try { req = JSON.parse(trimmed); } catch { return; }
|
|
99
|
+
handleRequest(req).then((result) => {
|
|
100
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
101
|
+
}).catch((err) => {
|
|
102
|
+
process.stdout.write(JSON.stringify({ issues: [], status: "error", error: String(err), elapsed_ms: 0 }) + "\n");
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
rl.on("close", () => { process.exit(0); });
|
|
Binary file
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const readline = require("readline");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const { spawnSync } = require("child_process");
|
|
6
|
+
|
|
7
|
+
function findPhp() {
|
|
8
|
+
const whichCmd = process.platform === "win32" ? "where.exe" : "which";
|
|
9
|
+
const r = spawnSync(whichCmd, ["php"], {
|
|
10
|
+
encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], windowsHide: true,
|
|
11
|
+
});
|
|
12
|
+
if (r.status === 0) {
|
|
13
|
+
const line = r.stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean)[0];
|
|
14
|
+
return line || null;
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function runPhpLint(root, files) {
|
|
20
|
+
const php = findPhp();
|
|
21
|
+
if (!php) return null;
|
|
22
|
+
|
|
23
|
+
const issues = [];
|
|
24
|
+
for (const f of files) {
|
|
25
|
+
const abs = path.isAbsolute(f) ? f : path.join(root, f);
|
|
26
|
+
let r;
|
|
27
|
+
try {
|
|
28
|
+
r = spawnSync(php, ["-l", abs], {
|
|
29
|
+
cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"],
|
|
30
|
+
windowsHide: true, timeout: 30000,
|
|
31
|
+
});
|
|
32
|
+
} catch (e) { continue; }
|
|
33
|
+
|
|
34
|
+
if (r.error) continue;
|
|
35
|
+
|
|
36
|
+
const output = (r.stdout || "") + (r.stderr || "");
|
|
37
|
+
if (r.status !== 0) {
|
|
38
|
+
const rel = path.relative(root, abs).replace(/\\/g, "/");
|
|
39
|
+
const m = output.match(/Parse error:\s*(.+?)\s+in\s+.+?\s+on line\s+(\d+)/i);
|
|
40
|
+
if (m) {
|
|
41
|
+
issues.push({
|
|
42
|
+
file: rel,
|
|
43
|
+
line: parseInt(m[2], 10),
|
|
44
|
+
col: 1,
|
|
45
|
+
kind: "error",
|
|
46
|
+
message: m[1],
|
|
47
|
+
source: "php-deep-layer",
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
const trimmed = output.trim().split(/\r?\n/)[0] || "syntax error";
|
|
51
|
+
issues.push({
|
|
52
|
+
file: rel, line: 1, col: 1,
|
|
53
|
+
kind: "error", message: trimmed,
|
|
54
|
+
source: "php-deep-layer",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { issues };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function fallbackSyntaxCheck(root, files) {
|
|
63
|
+
const issues = [];
|
|
64
|
+
for (const f of files) {
|
|
65
|
+
const abs = path.isAbsolute(f) ? f : path.join(root, f);
|
|
66
|
+
let content;
|
|
67
|
+
try { content = fs.readFileSync(abs, "utf8"); } catch { continue; }
|
|
68
|
+
const rel = path.relative(root, abs).replace(/\\/g, "/");
|
|
69
|
+
const lines = content.split(/\r?\n/);
|
|
70
|
+
|
|
71
|
+
let braceDepth = 0;
|
|
72
|
+
let inSingleQuote = false, inDoubleQuote = false;
|
|
73
|
+
let inLineComment = false, inBlockComment = false;
|
|
74
|
+
let inHeredoc = null;
|
|
75
|
+
let unclosedLine = 0;
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < lines.length; i++) {
|
|
78
|
+
const line = lines[i];
|
|
79
|
+
|
|
80
|
+
if (inHeredoc) {
|
|
81
|
+
if (line.trim() === inHeredoc || line.trim() === inHeredoc + ";") {
|
|
82
|
+
inHeredoc = null;
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
inLineComment = false;
|
|
88
|
+
for (let j = 0; j < line.length; j++) {
|
|
89
|
+
const ch = line[j];
|
|
90
|
+
const next = j + 1 < line.length ? line[j + 1] : "";
|
|
91
|
+
|
|
92
|
+
if (inBlockComment) {
|
|
93
|
+
if (ch === "*" && next === "/") { inBlockComment = false; j++; }
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (inSingleQuote) {
|
|
97
|
+
if (ch === "\\") { j++; continue; }
|
|
98
|
+
if (ch === "'") inSingleQuote = false;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (inDoubleQuote) {
|
|
102
|
+
if (ch === "\\") { j++; continue; }
|
|
103
|
+
if (ch === '"') inDoubleQuote = false;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (inLineComment) continue;
|
|
107
|
+
|
|
108
|
+
if (ch === "/" && next === "/") { inLineComment = true; continue; }
|
|
109
|
+
if (ch === "#") { inLineComment = true; continue; }
|
|
110
|
+
if (ch === "/" && next === "*") { inBlockComment = true; j++; continue; }
|
|
111
|
+
if (ch === "'") { inSingleQuote = true; unclosedLine = i + 1; continue; }
|
|
112
|
+
if (ch === '"') { inDoubleQuote = true; unclosedLine = i + 1; continue; }
|
|
113
|
+
if (ch === "<" && next === "<" && j + 2 < line.length && line[j + 2] === "<") {
|
|
114
|
+
const rest = line.slice(j + 3).trim().replace(/^'|'$/g, "").replace(/^"|"$/g, "");
|
|
115
|
+
if (rest) { inHeredoc = rest; }
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
if (ch === "{") braceDepth++;
|
|
119
|
+
else if (ch === "}") braceDepth--;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (braceDepth > 0) {
|
|
123
|
+
issues.push({ file: rel, line: lines.length, col: 1,
|
|
124
|
+
kind: "syntax_error", message: `Unmatched opening brace (${braceDepth} unclosed)`,
|
|
125
|
+
source: "php-deep-layer" });
|
|
126
|
+
} else if (braceDepth < 0) {
|
|
127
|
+
issues.push({ file: rel, line: 1, col: 1,
|
|
128
|
+
kind: "syntax_error", message: `Unmatched closing brace (${-braceDepth} extra)`,
|
|
129
|
+
source: "php-deep-layer" });
|
|
130
|
+
}
|
|
131
|
+
if (inDoubleQuote || inSingleQuote) {
|
|
132
|
+
issues.push({ file: rel, line: unclosedLine, col: 1,
|
|
133
|
+
kind: "syntax_error", message: "Unclosed string literal",
|
|
134
|
+
source: "php-deep-layer" });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return issues;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function handleRequest(req) {
|
|
141
|
+
const t0 = Date.now();
|
|
142
|
+
const root = req.root || process.cwd();
|
|
143
|
+
const files = req.files || [];
|
|
144
|
+
if (files.length === 0) {
|
|
145
|
+
return { issues: [], status: "clean", reason: "no_files", elapsed_ms: 0 };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const lintResult = runPhpLint(root, files);
|
|
149
|
+
if (lintResult) {
|
|
150
|
+
return {
|
|
151
|
+
issues: lintResult.issues,
|
|
152
|
+
status: lintResult.issues.length > 0 ? "php_error" : "clean",
|
|
153
|
+
reason: "php",
|
|
154
|
+
elapsed_ms: Date.now() - t0,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const fallbackIssues = fallbackSyntaxCheck(root, files);
|
|
159
|
+
return {
|
|
160
|
+
issues: fallbackIssues,
|
|
161
|
+
status: fallbackIssues.length > 0 ? "syntax_error" : "clean",
|
|
162
|
+
reason: fallbackIssues.length > 0 ? "fallback" : "fallback_clean",
|
|
163
|
+
elapsed_ms: Date.now() - t0,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
168
|
+
rl.on("line", (line) => {
|
|
169
|
+
const trimmed = line.trim();
|
|
170
|
+
if (!trimmed) return;
|
|
171
|
+
let req;
|
|
172
|
+
try { req = JSON.parse(trimmed); } catch { return; }
|
|
173
|
+
handleRequest(req).then((result) => {
|
|
174
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
175
|
+
}).catch((err) => {
|
|
176
|
+
process.stdout.write(JSON.stringify({ issues: [], status: "error", error: String(err), elapsed_ms: 0 }) + "\n");
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
rl.on("close", () => { process.exit(0); });
|