ifc-language-server/client/node_modules/vscode-languageclient/lib/node/main.js
Ryan Schultz 8afacf268a Implemented a working Language Server Protocol (LSP) for IFC files with:
- Hover provider showing entity information and type
- Go-to-definition (F12) for entity references
- Basic IFC file validation (ISO-10303-21 header check)
- Entity parsing with regex-based detection
- Proper CommonJS module system (avoiding ES module issues)

This replaces the broken baseline from ifc-developer-tools which had:
- Non-functional ES module configuration
- Circular dependency issues
- Parser crashes
- Non-working PositionVisitor

Built on Microsoft's LSP example template for a clean, maintainable foundation.

Next: Add hierarchical entity dependency tree in hover tooltip."
2025-12-07 10:20:07 -06:00

570 lines
27 KiB
JavaScript

"use strict";
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SettingMonitor = exports.LanguageClient = exports.TransportKind = void 0;
const cp = require("child_process");
const fs = require("fs");
const path = require("path");
const vscode_1 = require("vscode");
const Is = require("../common/utils/is");
const client_1 = require("../common/client");
const processes_1 = require("./processes");
const node_1 = require("vscode-languageserver-protocol/node");
// Import SemVer functions individually to avoid circular dependencies in SemVer
const semverParse = require("semver/functions/parse");
const semverSatisfies = require("semver/functions/satisfies");
__exportStar(require("vscode-languageserver-protocol/node"), exports);
__exportStar(require("../common/api"), exports);
const REQUIRED_VSCODE_VERSION = '^1.82.0'; // do not change format, updated by `updateVSCode` script
var TransportKind;
(function (TransportKind) {
TransportKind[TransportKind["stdio"] = 0] = "stdio";
TransportKind[TransportKind["ipc"] = 1] = "ipc";
TransportKind[TransportKind["pipe"] = 2] = "pipe";
TransportKind[TransportKind["socket"] = 3] = "socket";
})(TransportKind || (exports.TransportKind = TransportKind = {}));
var Transport;
(function (Transport) {
function isSocket(value) {
const candidate = value;
return candidate && candidate.kind === TransportKind.socket && Is.number(candidate.port);
}
Transport.isSocket = isSocket;
})(Transport || (Transport = {}));
var Executable;
(function (Executable) {
function is(value) {
return Is.string(value.command);
}
Executable.is = is;
})(Executable || (Executable = {}));
var NodeModule;
(function (NodeModule) {
function is(value) {
return Is.string(value.module);
}
NodeModule.is = is;
})(NodeModule || (NodeModule = {}));
var StreamInfo;
(function (StreamInfo) {
function is(value) {
let candidate = value;
return candidate && candidate.writer !== undefined && candidate.reader !== undefined;
}
StreamInfo.is = is;
})(StreamInfo || (StreamInfo = {}));
var ChildProcessInfo;
(function (ChildProcessInfo) {
function is(value) {
let candidate = value;
return candidate && candidate.process !== undefined && typeof candidate.detached === 'boolean';
}
ChildProcessInfo.is = is;
})(ChildProcessInfo || (ChildProcessInfo = {}));
class LanguageClient extends client_1.BaseLanguageClient {
constructor(arg1, arg2, arg3, arg4, arg5) {
let id;
let name;
let serverOptions;
let clientOptions;
let forceDebug;
if (Is.string(arg2)) {
id = arg1;
name = arg2;
serverOptions = arg3;
clientOptions = arg4;
forceDebug = !!arg5;
}
else {
id = arg1.toLowerCase();
name = arg1;
serverOptions = arg2;
clientOptions = arg3;
forceDebug = arg4;
}
if (forceDebug === undefined) {
forceDebug = false;
}
super(id, name, clientOptions);
this._serverOptions = serverOptions;
this._forceDebug = forceDebug;
this._isInDebugMode = forceDebug;
try {
this.checkVersion();
}
catch (error) {
if (Is.string(error.message)) {
this.outputChannel.appendLine(error.message);
}
throw error;
}
}
checkVersion() {
const codeVersion = semverParse(vscode_1.version);
if (!codeVersion) {
throw new Error(`No valid VS Code version detected. Version string is: ${vscode_1.version}`);
}
// Remove the insider pre-release since we stay API compatible.
if (codeVersion.prerelease && codeVersion.prerelease.length > 0) {
codeVersion.prerelease = [];
}
if (!semverSatisfies(codeVersion, REQUIRED_VSCODE_VERSION)) {
throw new Error(`The language client requires VS Code version ${REQUIRED_VSCODE_VERSION} but received version ${vscode_1.version}`);
}
}
get isInDebugMode() {
return this._isInDebugMode;
}
async restart() {
await this.stop();
// We are in debug mode. Wait a little before we restart
// so that the debug port can be freed. We can safely ignore
// the disposable returned from start since it will call
// stop on the same client instance.
if (this.isInDebugMode) {
await new Promise((resolve) => setTimeout(resolve, 1000));
await this.start();
}
else {
await this.start();
}
}
stop(timeout = 2000) {
return super.stop(timeout).finally(() => {
if (this._serverProcess) {
const toCheck = this._serverProcess;
this._serverProcess = undefined;
if (this._isDetached === undefined || !this._isDetached) {
this.checkProcessDied(toCheck);
}
this._isDetached = undefined;
}
});
}
checkProcessDied(childProcess) {
if (!childProcess || childProcess.pid === undefined) {
return;
}
setTimeout(() => {
// Test if the process is still alive. Throws an exception if not
try {
if (childProcess.pid !== undefined) {
process.kill(childProcess.pid, 0);
(0, processes_1.terminate)(childProcess);
}
}
catch (error) {
// All is fine.
}
}, 2000);
}
handleConnectionClosed() {
this._serverProcess = undefined;
return super.handleConnectionClosed();
}
fillInitializeParams(params) {
super.fillInitializeParams(params);
if (params.processId === null) {
params.processId = process.pid;
}
}
createMessageTransports(encoding) {
function getEnvironment(env, fork) {
if (!env && !fork) {
return undefined;
}
const result = Object.create(null);
Object.keys(process.env).forEach(key => result[key] = process.env[key]);
if (fork) {
result['ELECTRON_RUN_AS_NODE'] = '1';
result['ELECTRON_NO_ASAR'] = '1';
}
if (env) {
Object.keys(env).forEach(key => result[key] = env[key]);
}
return result;
}
const debugStartWith = ['--debug=', '--debug-brk=', '--inspect=', '--inspect-brk='];
const debugEquals = ['--debug', '--debug-brk', '--inspect', '--inspect-brk'];
function startedInDebugMode() {
let args = process.execArgv;
if (args) {
return args.some((arg) => {
return debugStartWith.some(value => arg.startsWith(value)) ||
debugEquals.some(value => arg === value);
});
}
return false;
}
function assertStdio(process) {
if (process.stdin === null || process.stdout === null || process.stderr === null) {
throw new Error('Process created without stdio streams');
}
}
const server = this._serverOptions;
// We got a function.
if (Is.func(server)) {
return server().then((result) => {
if (client_1.MessageTransports.is(result)) {
this._isDetached = !!result.detached;
return result;
}
else if (StreamInfo.is(result)) {
this._isDetached = !!result.detached;
return { reader: new node_1.StreamMessageReader(result.reader), writer: new node_1.StreamMessageWriter(result.writer) };
}
else {
let cp;
if (ChildProcessInfo.is(result)) {
cp = result.process;
this._isDetached = result.detached;
}
else {
cp = result;
this._isDetached = false;
}
cp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
return { reader: new node_1.StreamMessageReader(cp.stdout), writer: new node_1.StreamMessageWriter(cp.stdin) };
}
});
}
let json;
let runDebug = server;
if (runDebug.run || runDebug.debug) {
if (this._forceDebug || startedInDebugMode()) {
json = runDebug.debug;
this._isInDebugMode = true;
}
else {
json = runDebug.run;
this._isInDebugMode = false;
}
}
else {
json = server;
}
return this._getServerWorkingDir(json.options).then(serverWorkingDir => {
if (NodeModule.is(json) && json.module) {
let node = json;
let transport = node.transport || TransportKind.stdio;
if (node.runtime) {
const args = [];
const options = node.options ?? Object.create(null);
if (options.execArgv) {
options.execArgv.forEach(element => args.push(element));
}
args.push(node.module);
if (node.args) {
node.args.forEach(element => args.push(element));
}
const execOptions = Object.create(null);
execOptions.cwd = serverWorkingDir;
execOptions.env = getEnvironment(options.env, false);
const runtime = this._getRuntimePath(node.runtime, serverWorkingDir);
let pipeName = undefined;
if (transport === TransportKind.ipc) {
// exec options not correctly typed in lib
execOptions.stdio = [null, null, null, 'ipc'];
args.push('--node-ipc');
}
else if (transport === TransportKind.stdio) {
args.push('--stdio');
}
else if (transport === TransportKind.pipe) {
pipeName = (0, node_1.generateRandomPipeName)();
args.push(`--pipe=${pipeName}`);
}
else if (Transport.isSocket(transport)) {
args.push(`--socket=${transport.port}`);
}
args.push(`--clientProcessId=${process.pid.toString()}`);
if (transport === TransportKind.ipc || transport === TransportKind.stdio) {
const serverProcess = cp.spawn(runtime, args, execOptions);
if (!serverProcess || !serverProcess.pid) {
return handleChildProcessStartError(serverProcess, `Launching server using runtime ${runtime} failed.`);
}
this._serverProcess = serverProcess;
serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
if (transport === TransportKind.ipc) {
serverProcess.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
return Promise.resolve({ reader: new node_1.IPCMessageReader(serverProcess), writer: new node_1.IPCMessageWriter(serverProcess) });
}
else {
return Promise.resolve({ reader: new node_1.StreamMessageReader(serverProcess.stdout), writer: new node_1.StreamMessageWriter(serverProcess.stdin) });
}
}
else if (transport === TransportKind.pipe) {
return (0, node_1.createClientPipeTransport)(pipeName).then((transport) => {
const process = cp.spawn(runtime, args, execOptions);
if (!process || !process.pid) {
return handleChildProcessStartError(process, `Launching server using runtime ${runtime} failed.`);
}
this._serverProcess = process;
process.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
process.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
return transport.onConnected().then((protocol) => {
return { reader: protocol[0], writer: protocol[1] };
});
});
}
else if (Transport.isSocket(transport)) {
return (0, node_1.createClientSocketTransport)(transport.port).then((transport) => {
const process = cp.spawn(runtime, args, execOptions);
if (!process || !process.pid) {
return handleChildProcessStartError(process, `Launching server using runtime ${runtime} failed.`);
}
this._serverProcess = process;
process.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
process.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
return transport.onConnected().then((protocol) => {
return { reader: protocol[0], writer: protocol[1] };
});
});
}
}
else {
let pipeName = undefined;
return new Promise((resolve, reject) => {
const args = (node.args && node.args.slice()) ?? [];
if (transport === TransportKind.ipc) {
args.push('--node-ipc');
}
else if (transport === TransportKind.stdio) {
args.push('--stdio');
}
else if (transport === TransportKind.pipe) {
pipeName = (0, node_1.generateRandomPipeName)();
args.push(`--pipe=${pipeName}`);
}
else if (Transport.isSocket(transport)) {
args.push(`--socket=${transport.port}`);
}
args.push(`--clientProcessId=${process.pid.toString()}`);
const options = node.options ?? Object.create(null);
options.env = getEnvironment(options.env, true);
options.execArgv = options.execArgv || [];
options.cwd = serverWorkingDir;
options.silent = true;
if (transport === TransportKind.ipc || transport === TransportKind.stdio) {
const sp = cp.fork(node.module, args || [], options);
assertStdio(sp);
this._serverProcess = sp;
sp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
if (transport === TransportKind.ipc) {
sp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
resolve({ reader: new node_1.IPCMessageReader(this._serverProcess), writer: new node_1.IPCMessageWriter(this._serverProcess) });
}
else {
resolve({ reader: new node_1.StreamMessageReader(sp.stdout), writer: new node_1.StreamMessageWriter(sp.stdin) });
}
}
else if (transport === TransportKind.pipe) {
(0, node_1.createClientPipeTransport)(pipeName).then((transport) => {
const sp = cp.fork(node.module, args || [], options);
assertStdio(sp);
this._serverProcess = sp;
sp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
sp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
transport.onConnected().then((protocol) => {
resolve({ reader: protocol[0], writer: protocol[1] });
}, reject);
}, reject);
}
else if (Transport.isSocket(transport)) {
(0, node_1.createClientSocketTransport)(transport.port).then((transport) => {
const sp = cp.fork(node.module, args || [], options);
assertStdio(sp);
this._serverProcess = sp;
sp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
sp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
transport.onConnected().then((protocol) => {
resolve({ reader: protocol[0], writer: protocol[1] });
}, reject);
}, reject);
}
});
}
}
else if (Executable.is(json) && json.command) {
const command = json;
const args = json.args !== undefined ? json.args.slice(0) : [];
let pipeName = undefined;
const transport = json.transport;
if (transport === TransportKind.stdio) {
args.push('--stdio');
}
else if (transport === TransportKind.pipe) {
pipeName = (0, node_1.generateRandomPipeName)();
args.push(`--pipe=${pipeName}`);
}
else if (Transport.isSocket(transport)) {
args.push(`--socket=${transport.port}`);
}
else if (transport === TransportKind.ipc) {
throw new Error(`Transport kind ipc is not support for command executable`);
}
const options = Object.assign({}, command.options);
options.cwd = options.cwd || serverWorkingDir;
if (transport === undefined || transport === TransportKind.stdio) {
const serverProcess = cp.spawn(command.command, args, options);
if (!serverProcess || !serverProcess.pid) {
return handleChildProcessStartError(serverProcess, `Launching server using command ${command.command} failed.`);
}
serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
this._serverProcess = serverProcess;
this._isDetached = !!options.detached;
return Promise.resolve({ reader: new node_1.StreamMessageReader(serverProcess.stdout), writer: new node_1.StreamMessageWriter(serverProcess.stdin) });
}
else if (transport === TransportKind.pipe) {
return (0, node_1.createClientPipeTransport)(pipeName).then((transport) => {
const serverProcess = cp.spawn(command.command, args, options);
if (!serverProcess || !serverProcess.pid) {
return handleChildProcessStartError(serverProcess, `Launching server using command ${command.command} failed.`);
}
this._serverProcess = serverProcess;
this._isDetached = !!options.detached;
serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
serverProcess.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
return transport.onConnected().then((protocol) => {
return { reader: protocol[0], writer: protocol[1] };
});
});
}
else if (Transport.isSocket(transport)) {
return (0, node_1.createClientSocketTransport)(transport.port).then((transport) => {
const serverProcess = cp.spawn(command.command, args, options);
if (!serverProcess || !serverProcess.pid) {
return handleChildProcessStartError(serverProcess, `Launching server using command ${command.command} failed.`);
}
this._serverProcess = serverProcess;
this._isDetached = !!options.detached;
serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
serverProcess.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
return transport.onConnected().then((protocol) => {
return { reader: protocol[0], writer: protocol[1] };
});
});
}
}
return Promise.reject(new Error(`Unsupported server configuration ` + JSON.stringify(server, null, 4)));
}).finally(() => {
if (this._serverProcess !== undefined) {
this._serverProcess.on('exit', (code, signal) => {
if (code !== null) {
this.error(`Server process exited with code ${code}.`, undefined, false);
}
if (signal !== null) {
this.error(`Server process exited with signal ${signal}.`, undefined, false);
}
});
}
});
}
_getRuntimePath(runtime, serverWorkingDirectory) {
if (path.isAbsolute(runtime)) {
return runtime;
}
const mainRootPath = this._mainGetRootPath();
if (mainRootPath !== undefined) {
const result = path.join(mainRootPath, runtime);
if (fs.existsSync(result)) {
return result;
}
}
if (serverWorkingDirectory !== undefined) {
const result = path.join(serverWorkingDirectory, runtime);
if (fs.existsSync(result)) {
return result;
}
}
return runtime;
}
_mainGetRootPath() {
let folders = vscode_1.workspace.workspaceFolders;
if (!folders || folders.length === 0) {
return undefined;
}
let folder = folders[0];
if (folder.uri.scheme === 'file') {
return folder.uri.fsPath;
}
return undefined;
}
_getServerWorkingDir(options) {
let cwd = options && options.cwd;
if (!cwd) {
cwd = this.clientOptions.workspaceFolder
? this.clientOptions.workspaceFolder.uri.fsPath
: this._mainGetRootPath();
}
if (cwd) {
// make sure the folder exists otherwise creating the process will fail
return new Promise(s => {
fs.lstat(cwd, (err, stats) => {
s(!err && stats.isDirectory() ? cwd : undefined);
});
});
}
return Promise.resolve(undefined);
}
}
exports.LanguageClient = LanguageClient;
class SettingMonitor {
constructor(_client, _setting) {
this._client = _client;
this._setting = _setting;
this._listeners = [];
}
start() {
vscode_1.workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this._listeners);
this.onDidChangeConfiguration();
return new vscode_1.Disposable(() => {
if (this._client.needsStop()) {
void this._client.stop();
}
});
}
onDidChangeConfiguration() {
let index = this._setting.indexOf('.');
let primary = index >= 0 ? this._setting.substr(0, index) : this._setting;
let rest = index >= 0 ? this._setting.substr(index + 1) : undefined;
let enabled = rest ? vscode_1.workspace.getConfiguration(primary).get(rest, false) : vscode_1.workspace.getConfiguration(primary);
if (enabled && this._client.needsStart()) {
this._client.start().catch((error) => this._client.error('Start failed after configuration change', error, 'force'));
}
else if (!enabled && this._client.needsStop()) {
void this._client.stop().catch((error) => this._client.error('Stop failed after configuration change', error, 'force'));
}
}
}
exports.SettingMonitor = SettingMonitor;
function handleChildProcessStartError(process, message) {
if (process === null) {
return Promise.reject(message);
}
return new Promise((_, reject) => {
process.on('error', (err) => {
reject(`${message} ${err}`);
});
// the error event should always be run immediately,
// but race on it just in case
setImmediate(() => reject(message));
});
}