typescript-language-server
Version:
Language Server Protocol (LSP) implementation for TypeScript using tsserver
157 lines • 6.34 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import fs from 'node:fs';
import { createRequire } from 'node:module';
import path from 'node:path';
import which from 'which';
import { pkgUpSync } from 'pkg-up';
import API from '../utils/api.js';
import { findPathToModule } from '../utils/modules-resolver.js';
export class TypeScriptVersion {
constructor(source, path, _pathLabel, logger) {
this.source = source;
this.path = path;
this._pathLabel = _pathLabel;
this.logger = logger;
this._api = null;
}
get tscPath() {
return path.resolve(this.path, '../bin/tsc');
}
get tsServerPath() {
return path.resolve(this.path, 'tsserver.js');
}
get pathLabel() {
return typeof this._pathLabel === 'undefined' ? this.path : this._pathLabel;
}
get isValid() {
return this.version !== null;
}
get version() {
if (this._api) {
return this._api;
}
this._api = this.getTypeScriptVersion(this.tsServerPath);
return this._api;
}
get versionString() {
const version = this.version;
return version ? version.displayName : null;
}
getTypeScriptVersion(serverPath) {
this.logger?.info(`Resolving TypeScript version from path "${serverPath}"...`);
if (!fs.existsSync(serverPath)) {
this.logger?.info('Server path does not exist on disk');
return null;
}
const p = serverPath.split(path.sep);
if (p.length <= 2) {
this.logger?.info('Server path is invalid (has less than two path components).');
return null;
}
const p2 = p.slice(0, -2);
const modulePath = p2.join(path.sep);
let fileName = path.join(modulePath, 'package.json');
if (!fs.existsSync(fileName)) {
// Special case for ts dev versions
if (path.basename(modulePath) === 'built') {
fileName = path.join(modulePath, '..', 'package.json');
}
}
if (!fs.existsSync(fileName)) {
this.logger?.info(`Failed to find package.json at path "${fileName}"`);
return null;
}
this.logger?.info(`Reading version from package.json at "${fileName}"`);
const contents = fs.readFileSync(fileName).toString();
let desc = null;
try {
desc = JSON.parse(contents);
}
catch (err) {
this.logger?.info('Failed parsing contents of package.json.');
return null;
}
if (!desc || !desc.version) {
this.logger?.info('Failed reading version number from package.json.');
return null;
}
this.logger?.info(`Resolved TypeScript version to "${desc.version}"`);
return API.fromVersionString(desc.version);
}
}
export const MODULE_FOLDERS = ['node_modules/typescript/lib', '.vscode/pnpify/typescript/lib', '.yarn/sdks/typescript/lib'];
export class TypeScriptVersionProvider {
constructor(configuration, logger) {
this.configuration = configuration;
this.logger = logger;
}
getUserSettingVersion() {
const { tsserverPath } = this.configuration || {};
if (!tsserverPath) {
return null;
}
this.logger?.info(`Resolving user-provided tsserver path "${tsserverPath}"...`);
let resolvedPath = tsserverPath;
// Resolve full path to the binary if path is not absolute.
if (!path.isAbsolute(resolvedPath)) {
const binaryPath = which.sync(tsserverPath, { nothrow: true });
if (binaryPath) {
resolvedPath = binaryPath;
}
this.logger?.info(`Non-absolute tsserver path resolved to "${binaryPath ? resolvedPath : '<failed>'}"`);
}
// Resolve symbolic link.
let stat = fs.lstatSync(resolvedPath, { throwIfNoEntry: false });
if (stat?.isSymbolicLink()) {
resolvedPath = fs.realpathSync(resolvedPath);
this.logger?.info(`Symbolic link tsserver path resolved to "${resolvedPath}"`);
}
// Get directory path
stat = fs.lstatSync(resolvedPath, { throwIfNoEntry: false });
if (stat?.isFile()) {
resolvedPath = path.dirname(resolvedPath);
this.logger?.info(`Resolved directory path from a file path: ${resolvedPath}`);
}
// Resolve path to the "lib" dir.
try {
const packageJsonPath = pkgUpSync({ cwd: resolvedPath });
this.logger?.info(`Resolved package.json location: "${packageJsonPath}"`);
if (packageJsonPath) {
resolvedPath = path.join(path.dirname(packageJsonPath), 'lib');
this.logger?.info(`Assumed tsserver lib location: "${resolvedPath}"`);
}
}
catch {
// ignore
}
return new TypeScriptVersion("user-setting" /* TypeScriptVersionSource.UserSetting */, resolvedPath, undefined, this.logger);
}
getWorkspaceVersion(workspaceFolders) {
for (const p of workspaceFolders) {
const libFolder = findPathToModule(p, MODULE_FOLDERS);
if (libFolder) {
const version = new TypeScriptVersion("workspace" /* TypeScriptVersionSource.Workspace */, libFolder);
if (version.isValid) {
return version;
}
}
}
return null;
}
bundledVersion() {
const require = createRequire(import.meta.url);
try {
const file = require.resolve('typescript');
const bundledVersion = new TypeScriptVersion("bundled" /* TypeScriptVersionSource.Bundled */, path.dirname(file), '');
return bundledVersion;
}
catch (e) {
// window.showMessage('Bundled typescript module not found', 'error');
return null;
}
}
}
//# sourceMappingURL=versionProvider.js.map