UNPKG

typescript-language-server

Version:

Language Server Protocol (LSP) implementation for TypeScript using tsserver

157 lines 6.34 kB
/*--------------------------------------------------------------------------------------------- * 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