Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
.filter(uri => uri.startsWith(packageRootUri))
.mergeMap(uri => this.updater.ensure(uri, span))
.toArray()
.mergeMap(() => {
span.log({ event: 'fetched package files' });
const config = this.projectManager.getParentConfiguration(packageRootUri, 'ts');
if (!config) {
throw new Error(`Could not find tsconfig for ${packageRootUri}`);
}
// Don't match PackageDescriptor on symbols
return this._getSymbolsInConfig(config, params.query || omit(params.symbol!, 'package'), limit, span);
});
}
// Regular workspace symbol search
// Search all symbols in own code, but not in dependencies
return Observable.from(this.projectManager.ensureOwnFiles(span))
.mergeMap(() =>
params.symbol && params.symbol.package && params.symbol.package.name
// If SymbolDescriptor query with PackageDescriptor, search for package.jsons with matching package name
? observableFromIterable(this.packageManager.packageJsonUris())
.filter(packageJsonUri => (JSON.parse(this.inMemoryFileSystem.getContent(packageJsonUri)) as PackageJson).name === params.symbol!.package!.name)
// Find their parent and child tsconfigs
.mergeMap(packageJsonUri => Observable.merge(
castArray(this.projectManager.getParentConfiguration(packageJsonUri) || []),
// Search child directories starting at the directory of the package.json
observableFromIterable(this.projectManager.getChildConfigurations(url.resolve(packageJsonUri, '.')))
))
// Else search all tsconfigs in the workspace
: observableFromIterable(this.projectManager.configurations())
)
// If PackageDescriptor is given, only search project with the matching package name
.mergeMap(config => this._getSymbolsInConfig(config, params.query || params.symbol, limit, span));
protected _getPackageDescriptor(uri: string): Observable {
// Get package name of the dependency in which the symbol is defined in, if any
const packageName = extractNodeModulesPackageName(uri);
if (packageName) {
// The symbol is part of a dependency in node_modules
// Build URI to package.json of the Dependency
const encodedPackageName = packageName.split('/').map(encodeURIComponent).join('/');
const parts = url.parse(uri);
const packageJsonUri = url.format({ ...parts, pathname: parts.pathname!.slice(0, parts.pathname!.lastIndexOf('/node_modules/' + encodedPackageName)) + `/node_modules/${encodedPackageName}/package.json` });
// Fetch the package.json of the dependency
return Observable.from(this.updater.ensure(packageJsonUri))
.map((): PackageDescriptor | undefined => {
const packageJson = JSON.parse(this.inMemoryFileSystem.getContent(packageJsonUri));
const { name, version } = packageJson;
if (name) {
// Used by the LSP proxy to shortcut database lookup of repo URL for PackageDescriptor
let repoURL: string | undefined;
if (name.startsWith('@types/')) {
// if the dependency package is an @types/ package, point the repo to DefinitelyTyped
repoURL = 'https://github.com/DefinitelyTyped/DefinitelyTyped';
} else {
// else use repository field from package.json
repoURL = typeof packageJson.repository === 'object' ? packageJson.repository.url : undefined;
}
return { name, version, repoURL };
}
return undefined;
return (() => {
try {
config.ensureAllFiles(span);
const program = config.getProgram(span);
if (!program) {
return Observable.empty();
}
if (query) {
let items: Observable<[number, ts.NavigateToItem]>;
if (typeof query === 'string') {
// Query by text query
items = Observable.from(config.getService().getNavigateToItems(query, limit, undefined, false))
// Same score for all
.map(item => [1, item]);
} else {
const queryWithoutPackage = omit(query, 'package') as SymbolDescriptor;
// Query by name
items = Observable.from(config.getService().getNavigateToItems(query.name || '', limit, undefined, false))
// Get a score how good the symbol matches the SymbolDescriptor (ignoring PackageDescriptor)
.map((item): [number, ts.NavigateToItem] => [getMatchScore(queryWithoutPackage, {
kind: item.kind,
name: item.name,
containerKind: item.containerKind,
containerName: item.containerName
}), item])
// If score === 0, no properties matched
.filter(([score, symbol]) => score > 0)
// If SymbolDescriptor matched, get package.json and match PackageDescriptor name
textDocumentReferences(params: ReferenceParams, span = new Span()): Observable {
const uri = normalizeUri(params.textDocument.uri);
// Ensure all files were fetched to collect all references
return Observable.from(this.projectManager.ensureOwnFiles(span))
.mergeMap(() => {
// Convert URI to file path because TypeScript doesn't work with URIs
const fileName = uri2path(uri);
// Get tsconfig configuration for requested file
const configuration = this.projectManager.getConfiguration(fileName);
// Ensure all files have been added
configuration.ensureAllFiles(span);
const program = configuration.getProgram(span);
if (!program) {
return [];
}
// Get SourceFile object for requested file
const sourceFile = this._getSourceFile(configuration, fileName, span);
if (!sourceFile) {
throw new Error(`Source file ${fileName} does not exist`);
}
}), item])
// If score === 0, no properties matched
.filter(([score, symbol]) => score > 0)
// If SymbolDescriptor matched, get package.json and match PackageDescriptor name
// TODO get and match full PackageDescriptor (version)
.mergeMap(([score, item]) => {
if (!query.package || !query.package.name) {
return [[score, item]];
}
const uri = path2uri('', item.fileName);
return Observable.from(this.packageManager.getClosestPackageJson(uri, span))
// If PackageDescriptor matches, increase score
.map((packageJson): [number, ts.NavigateToItem] => packageJson && packageJson.name === query.package!.name! ? [score + 1, item] : [score, item]);
});
}
return Observable.from(items)
// Map NavigateToItems to SymbolInformations
.map(([score, item]) => {
const sourceFile = program.getSourceFile(item.fileName);
if (!sourceFile) {
throw new Error(`Source file ${item.fileName} does not exist`);
}
const symbolInformation: SymbolInformation = {
name: item.name,
kind: convertStringtoSymbolKind(item.kind),
location: {
uri: this._defUri(item.fileName),
range: {
start: ts.getLineAndCharacterOfPosition(sourceFile, item.textSpan.start),
end: ts.getLineAndCharacterOfPosition(sourceFile, item.textSpan.start + item.textSpan.length)
}
}
this._initializeFileSystems(!this.options.strict && !(params.capabilities.xcontentProvider && params.capabilities.xfilesProvider));
this.updater = new FileSystemUpdater(this.fileSystem, this.inMemoryFileSystem);
this.projectManager = new ProjectManager(
this.root,
this.inMemoryFileSystem,
this.updater,
!!this.options.strict,
this.traceModuleResolution,
this.logger
);
this.packageManager = new PackageManager(this.updater, this.inMemoryFileSystem, this.logger);
// Detect DefinitelyTyped
// Fetch root package.json (if exists)
const normRootUri = this.rootUri.endsWith('/') ? this.rootUri : this.rootUri + '/';
const packageJsonUri = normRootUri + 'package.json';
this.isDefinitelyTyped = Observable.from(this.packageManager.getPackageJson(packageJsonUri, span))
// Check name
.map(packageJson => packageJson.name === 'definitely-typed')
.catch(err => [false])
.publishReplay()
.refCount();
// Pre-fetch files in the background if not DefinitelyTyped
this.isDefinitelyTyped
.mergeMap(isDefinitelyTyped => {
if (!isDefinitelyTyped) {
return this.projectManager.ensureOwnFiles(span);
}
return [];
})
.subscribe(undefined, err => {
this.logger.error(err);
.mergeMap(() => {
// Convert URI to file path
const fileName: string = uri2path(uri);
// Get closest tsconfig configuration
const configuration = this.projectManager.getConfiguration(fileName);
configuration.ensureBasicFiles(span);
const sourceFile = this._getSourceFile(configuration, fileName, span);
if (!sourceFile) {
throw new Error(`Unknown text document ${uri}`);
}
// Convert line/character to offset
const offset: number = ts.getPositionOfLineAndCharacter(sourceFile, params.position.line, params.position.character);
// Query TypeScript for references
return Observable.from(configuration.getService().getDefinitionAtPosition(fileName, offset) || [])
.mergeMap((definition: ts.DefinitionInfo): Observable => {
const definitionUri = this._defUri(definition.fileName);
// Get the PackageDescriptor
return this._getPackageDescriptor(definitionUri)
.map((packageDescriptor: PackageDescriptor | undefined): SymbolLocationInformation => {
const sourceFile = this._getSourceFile(configuration, definition.fileName, span);
if (!sourceFile) {
throw new Error(`Expected source file ${definition.fileName} to exist in configuration`);
}
const symbol = defInfoToSymbolDescriptor(definition);
if (packageDescriptor) {
symbol.package = packageDescriptor;
}
return {
symbol,
location: {
executeCodeFixCommand(fileTextChanges: ts.FileTextChanges[], span = new Span()): Observable {
if (fileTextChanges.length === 0) {
return Observable.throw(new Error('No changes supplied for code fix command'));
}
return Observable.from(this.projectManager.ensureOwnFiles(span))
.mergeMap(() => {
const configuration = this.projectManager.getConfiguration(fileTextChanges[0].fileName);
configuration.ensureBasicFiles(span);
const changes: {[uri: string]: TextEdit[]} = {};
for (const change of fileTextChanges) {
const sourceFile = this._getSourceFile(configuration, change.fileName, span);
if (!sourceFile) {
throw new Error(`Expected source file ${change.fileName} to exist in configuration`);
}
const uri = path2uri(this.root, change.fileName);
changes[uri] = change.textChanges.map(({ span, newText }): TextEdit => ({
range: {
start: ts.getLineAndCharacterOfPosition(sourceFile, span.start),
end: ts.getLineAndCharacterOfPosition(sourceFile, span.start + span.length)
},
.mergeMap(() => {
const fileName: string = uri2path(uri);
const configuration = this.projectManager.getConfiguration(fileName);
configuration.ensureBasicFiles(span);
const sourceFile = this._getSourceFile(configuration, fileName, span);
if (!sourceFile) {
throw new Error(`Expected source file ${fileName} to exist`);
}
const offset: number = ts.getPositionOfLineAndCharacter(sourceFile, params.position.line, params.position.character);
const definitions: ts.DefinitionInfo[] | undefined = configuration.getService().getDefinitionAtPosition(fileName, offset);
return Observable.from(definitions || [])
.map((definition): Location => {
const sourceFile = this._getSourceFile(configuration, definition.fileName, span);
if (!sourceFile) {
throw new Error('expected source file "' + definition.fileName + '" to exist in configuration');
}
const start = ts.getLineAndCharacterOfPosition(sourceFile, definition.textSpan.start);
const end = ts.getLineAndCharacterOfPosition(sourceFile, definition.textSpan.start + definition.textSpan.length);
return {
uri: this._defUri(definition.fileName),
range: {
start,
end
}
};
});
});
}
configuration.ensureAllFiles(span);
const sourceFile = this._getSourceFile(configuration, filePath, span);
if (!sourceFile) {
throw new Error(`Expected source file ${filePath} to exist in configuration`);
}
const position = ts.getPositionOfLineAndCharacter(sourceFile, params.position.line, params.position.character);
const renameInfo = configuration.getService().getRenameInfo(filePath, position);
if (!renameInfo.canRename) {
throw new Error('This symbol cannot be renamed');
}
return Observable.from(configuration.getService().findRenameLocations(filePath, position, false, true))
.map((location: ts.RenameLocation): [string, TextEdit] => {
const sourceFile = this._getSourceFile(configuration, location.fileName, span);
if (!sourceFile) {
throw new Error(`expected source file ${location.fileName} to exist in configuration`);
}
const editUri = path2uri(this.root, location.fileName);
const start = ts.getLineAndCharacterOfPosition(sourceFile, location.textSpan.start);
const end = ts.getLineAndCharacterOfPosition(sourceFile, location.textSpan.start + location.textSpan.length);
const edit: TextEdit = { range: { start, end }, newText: params.newName };
return [editUri, edit];
});
})
.map(([uri, edit]): AddPatch => {