UNPKG

javascript-typescript-langserver

Version:

Implementation of the Language Server Protocol for JavaScript and TypeScript

221 lines 7.86 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = require("events"); const fs = require("fs"); const path = require("path"); const ts = require("typescript"); const logging_1 = require("./logging"); const match_files_1 = require("./match-files"); const util_1 = require("./util"); /** * TypeScript library files fetched from the local file system (bundled TS) */ exports.typeScriptLibraries = new Map(); /** * In-memory file system, can be served as a ParseConfigHost (thus allowing listing files that belong to project based on tsconfig.json options) */ class InMemoryFileSystem extends events_1.EventEmitter { constructor(path, logger = new logging_1.NoopLogger()) { super(); this.logger = logger; /** * Contains a Map of all URIs that exist in the workspace, optionally with a content. * File contents for URIs in it do not neccessarily have to be fetched already. */ this.files = new Map(); this.path = path; this.overlay = new Map(); this.rootNode = { file: false, children: new Map() }; } /** Emitted when a file was added */ on(event, listener) { return super.on(event, listener); } /** * Returns an IterableIterator for all URIs known to exist in the workspace (content loaded or not) */ uris() { return this.files.keys(); } /** * Adds a file to the local cache * * @param uri The URI of the file * @param content The optional content */ add(uri, content) { // Make sure not to override existing content with undefined if (content !== undefined || !this.files.has(uri)) { this.files.set(uri, content); } // Add to directory tree // TODO: convert this to use URIs. const filePath = util_1.uri2path(uri); const components = filePath.split(/[\/\\]/).filter(c => c); let node = this.rootNode; for (const [i, component] of components.entries()) { const n = node.children.get(component); if (!n) { if (i < components.length - 1) { const n = { file: false, children: new Map() }; node.children.set(component, n); node = n; } else { node.children.set(component, { file: true, children: new Map() }); } } else { node = n; } } this.emit('add', uri, content); } /** * Returns true if the given file is known to exist in the workspace (content loaded or not) * * @param uri URI to a file */ has(uri) { return this.files.has(uri) || this.fileExists(util_1.uri2path(uri)); } /** * Returns the file content for the given URI. * Will throw an Error if no available in-memory. * Use FileSystemUpdater.ensure() to ensure that the file is available. */ getContent(uri) { let content = this.overlay.get(uri); if (content === undefined) { content = this.files.get(uri); } if (content === undefined) { content = exports.typeScriptLibraries.get(util_1.uri2path(uri)); } if (content === undefined) { throw new Error(`Content of ${uri} is not available in memory`); } return content; } /** * Tells if a file denoted by the given name exists in the workspace (does not have to be loaded) * * @param path File path or URI (both absolute or relative file paths are accepted) */ fileExists(path) { const uri = util_1.path2uri(path); return this.overlay.has(uri) || this.files.has(uri) || exports.typeScriptLibraries.has(path); } /** * @param path file path (both absolute or relative file paths are accepted) * @return file's content in the following order (overlay then cache). * If there is no such file, returns empty string to match expected signature */ readFile(path) { const content = this.readFileIfExists(path); if (content === undefined) { this.logger.warn(`readFile ${path} requested by TypeScript but content not available`); return ''; } return content; } /** * @param path file path (both absolute or relative file paths are accepted) * @return file's content in the following order (overlay then cache). * If there is no such file, returns undefined */ readFileIfExists(path) { const uri = util_1.path2uri(path); let content = this.overlay.get(uri); if (content !== undefined) { return content; } // TODO This assumes that the URI was a file:// URL. // In reality it could be anything, and the first URI matching the path should be used. // With the current Map, the search would be O(n), it would require a tree to get O(log(n)) content = this.files.get(uri); if (content !== undefined) { return content; } return exports.typeScriptLibraries.get(path); } /** * Invalidates temporary content denoted by the given URI * @param uri file's URI */ didClose(uri) { this.overlay.delete(uri); } /** * Adds temporary content denoted by the given URI * @param uri file's URI */ didSave(uri) { const content = this.overlay.get(uri); if (content !== undefined) { this.add(uri, content); } } /** * Updates temporary content denoted by the given URI * @param uri file's URI */ didChange(uri, text) { this.overlay.set(uri, text); } /** * Called by TS service to scan virtual directory when TS service looks for source files that belong to a project */ readDirectory(rootDir, extensions, excludes, includes) { return match_files_1.matchFiles(rootDir, extensions, excludes, includes, true, this.path, p => this.getFileSystemEntries(p)); } /** * Called by TS service to scan virtual directory when TS service looks for source files that belong to a project */ getFileSystemEntries(path) { const ret = { files: [], directories: [] }; let node = this.rootNode; const components = path.split('/').filter(c => c); if (components.length !== 1 || components[0]) { for (const component of components) { const n = node.children.get(component); if (!n) { return ret; } node = n; } } for (const [name, value] of node.children.entries()) { if (value.file) { ret.files.push(name); } else { ret.directories.push(name); } } return ret; } trace(message) { this.logger.log(message); } } exports.InMemoryFileSystem = InMemoryFileSystem; /** * Fetching TypeScript library files from local file system */ const libPath = path.dirname(ts.getDefaultLibFilePath({ target: ts.ScriptTarget.ES2015 })); for (const file of fs.readdirSync(libPath)) { const fullPath = path.join(libPath, file); if (fs.statSync(fullPath).isFile()) { exports.typeScriptLibraries.set(util_1.toUnixPath(fullPath), fs.readFileSync(fullPath).toString()); } } /** * @param path file path * @return true if given file belongs to bundled TypeScript libraries */ function isTypeScriptLibrary(path) { return exports.typeScriptLibraries.has(util_1.toUnixPath(path)); } exports.isTypeScriptLibrary = isTypeScriptLibrary; //# sourceMappingURL=memfs.js.map