UNPKG

javascript-typescript-langserver

Version:

Implementation of the Language Server Protocol for JavaScript and TypeScript

167 lines 6.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = require("events"); const opentracing_1 = require("opentracing"); const path = require("path"); const rxjs_1 = require("rxjs"); const url = require("url"); const logging_1 = require("./logging"); const tracing_1 = require("./tracing"); exports.DEPENDENCY_KEYS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']; /** * Matches: * * /foo/node_modules/(bar)/index.d.ts * /foo/node_modules/bar/node_modules/(baz)/index.d.ts * /foo/node_modules/(@types/bar)/index.ts */ const NODE_MODULES_PACKAGE_NAME_REGEXP = /.*\/node_modules\/((?:@[^\/]+\/)?[^\/]+)\/.*$/; /** * Returns the name of a package that a file is contained in */ function extractNodeModulesPackageName(uri) { const match = decodeURIComponent(url.parse(uri).pathname || '').match(NODE_MODULES_PACKAGE_NAME_REGEXP); return match ? match[1] : undefined; } exports.extractNodeModulesPackageName = extractNodeModulesPackageName; /** * Matches: * * /foo/types/(bar)/index.d.ts * /foo/types/bar/node_modules/(baz)/index.d.ts * /foo/types/(@types/bar)/index.ts */ const DEFINITELY_TYPED_PACKAGE_NAME_REGEXP = /\/types\/((?:@[^\/]+\/)?[^\/]+)\/.*$/; /** * Returns the name of a package that a file in DefinitelyTyped defines. * E.g. `file:///foo/types/node/index.d.ts` -> `@types/node` */ function extractDefinitelyTypedPackageName(uri) { const match = decodeURIComponent(url.parse(uri).pathname || '').match(DEFINITELY_TYPED_PACKAGE_NAME_REGEXP); return match ? '@types/' + match[1] : undefined; } exports.extractDefinitelyTypedPackageName = extractDefinitelyTypedPackageName; class PackageManager extends events_1.EventEmitter { constructor(updater, inMemoryFileSystem, logger = new logging_1.NoopLogger()) { super(); this.updater = updater; this.inMemoryFileSystem = inMemoryFileSystem; this.logger = logger; /** * Map of package.json URIs _defined_ in the workspace to optional content. * Does not include package.jsons of dependencies. * Updated as new package.jsons are discovered. */ this.packages = new Map(); /** * Subscriptions to unsubscribe from on object disposal */ this.subscriptions = new rxjs_1.Subscription(); let rootPackageJsonLevel = Infinity; // Find locations of package.jsons _not_ inside node_modules this.subscriptions.add(rxjs_1.Observable.fromEvent(this.inMemoryFileSystem, 'add', (k, v) => [k, v]).subscribe(([uri, content]) => { const parts = url.parse(uri); if (!parts.pathname || !parts.pathname.endsWith('/package.json') || parts.pathname.includes('/node_modules/')) { return; } let parsed; if (content) { try { parsed = JSON.parse(content); } catch (err) { logger.error(`Error parsing package.json:`, err); } } // Don't override existing content with undefined if (parsed || !this.packages.get(uri)) { this.packages.set(uri, parsed); this.logger.log(`Found package ${uri}`); this.emit('parsed', uri, parsed); } // If the current root package.json is further nested than this one, replace it const level = parts.pathname.split('/').length; if (level < rootPackageJsonLevel) { this.rootPackageJsonUri = uri; rootPackageJsonLevel = level; } })); } dispose() { this.subscriptions.unsubscribe(); } on(event, listener) { return super.on(event, listener); } /** * Returns an Iterable for all package.jsons in the workspace */ packageJsonUris() { return this.packages.keys(); } /** * Gets the content of the closest package.json known to to the DependencyManager in the ancestors of a URI * * @return Observable that emits a single PackageJson or never */ getClosestPackageJson(uri, span = new opentracing_1.Span()) { return this.updater.ensureStructure().concat(rxjs_1.Observable.defer(() => { const packageJsonUri = this.getClosestPackageJsonUri(uri); if (!packageJsonUri) { return rxjs_1.Observable.empty(); } return this.getPackageJson(packageJsonUri, span); })); } /** * Returns the parsed package.json of the passed URI * * @param uri URI of the package.json * @return Observable that emits a single PackageJson or never */ getPackageJson(uri, childOf = new opentracing_1.Span()) { return tracing_1.traceObservable('Get package.json', childOf, span => { span.addTags({ uri }); if (uri.includes('/node_modules/')) { return rxjs_1.Observable.throw(new Error(`Not an own package.json: ${uri}`)); } let packageJson = this.packages.get(uri); if (packageJson) { return rxjs_1.Observable.of(packageJson); } return this.updater.ensure(uri, span).concat(rxjs_1.Observable.defer(() => { packageJson = this.packages.get(uri); if (!packageJson) { return rxjs_1.Observable.throw(new Error(`Expected ${uri} to be registered in PackageManager`)); } return rxjs_1.Observable.of(packageJson); })); }); } /** * Walks the parent directories of a given URI to find the first package.json that is known to the InMemoryFileSystem * * @param uri URI of a file or directory in the workspace * @return The found package.json or undefined if none found */ getClosestPackageJsonUri(uri) { const parts = url.parse(uri); while (true) { if (!parts.pathname) { return undefined; } const packageJsonUri = url.format(Object.assign({}, parts, { pathname: path.posix.join(parts.pathname, 'package.json') })); if (this.packages.has(packageJsonUri)) { return packageJsonUri; } if (parts.pathname === '/') { return undefined; } parts.pathname = path.posix.dirname(parts.pathname); } } } exports.PackageManager = PackageManager; //# sourceMappingURL=packages.js.map