javascript-typescript-langserver
Version:
Implementation of the Language Server Protocol for JavaScript and TypeScript
167 lines • 6.68 kB
JavaScript
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
;