alm
Version:
The best IDE for TypeScript
759 lines (656 loc) • 29.8 kB
text/typescript
/**
* The stuff from project that the frontend queries.
* Stuff like autocomplete is a good example.
* Errors etc are pushed automatically from `activeProject` and do not belong here :)
*/
import { Project } from "./core/project";
import * as activeProject from "./activeProject";
let getProject = activeProject.GetProject.ifCurrentOrErrorOut;
import { Types } from "../../../socket/socketContract";
import * as types from "../../../common/types";
import * as utils from "../../../common/utils";
let { resolve } = utils;
import * as fsu from "../../utils/fsu";
import { errorsCache } from "./cache/tsErrorsCache";
import { getPathCompletionsForAutocomplete } from "./modules/getPathCompletions";
export function getCompletionsAtPosition(query: Types.GetCompletionsAtPositionQuery): Promise<Types.GetCompletionsAtPositionResponse> {
const { filePath, position, prefix } = query;
const project = getProject(query.filePath);
const service = project.languageService;
const languageServiceHost = project.languageServiceHost;
const completions: ts.CompletionInfo = service.getCompletionsAtPosition(filePath, position, undefined);
let completionList = completions ? completions.entries.filter(x => !!x) : [];
const endsInPunctuation = utils.prefixEndsInPunctuation(prefix);
/** Doing too many suggestions is slowing us down in some cases */
let maxSuggestions = 10000;
// limit to maxSuggestions
if (completionList.length > maxSuggestions) completionList = completionList.slice(0, maxSuggestions);
let completionsToReturn: Types.Completion[] = completionList.map((c, index) => {
return {
name: c.name,
kind: c.kind,
comment: '',
display: ''
};
});
/**
* Add function signature help
*/
if (query.prefix == '(') {
const signatures = service.getSignatureHelpItems(query.filePath, query.position);
if (signatures && signatures.items) {
signatures.items.forEach((item, index) => {
const template: string = item.parameters.map((p, i) => {
const display = '{{' + (i + 1) + ':' + ts.displayPartsToString(p.displayParts) + '}}';
return display;
}).join(ts.displayPartsToString(item.separatorDisplayParts));
const name: string = item.parameters.map((p) => ts.displayPartsToString(p.displayParts))
.join(ts.displayPartsToString(item.separatorDisplayParts));
// e.g. test(something:string):any;
// prefix: test(
// template: {{something}}
// suffix: ): any;
const description: string =
ts.displayPartsToString(item.prefixDisplayParts)
+ template
+ ts.displayPartsToString(item.suffixDisplayParts);
completionsToReturn.unshift({
kind: types.completionKindSnippet,
/** We use `(sig)` prefix to make sure its sorted by monaco to be at the top */
name: '(sig) ' + name,
insertText: template,
display: 'function signature',
comment: `Overload ${index + 1} of ${signatures.items.length}`
});
});
}
}
/**
* Add file path completions
*/
const pathCompletions = getPathCompletionsForAutocomplete({
position,
project,
filePath,
prefix
});
if (pathCompletions.length) {
completionsToReturn = pathCompletions.map(f => {
const result: types.Completion = {
kind: types.completionKindPath,
name: f.relativePath,
display: f.fileName,
comment: f.fullPath,
textEdit: {
from: languageServiceHost.getLineAndCharacterOfPosition(filePath, f.pathStringRange.from),
to: languageServiceHost.getLineAndCharacterOfPosition(filePath, f.pathStringRange.to),
newText: f.relativePath
}
};
return result;
}).concat(completionsToReturn);
}
return resolve({
completions: completionsToReturn,
endsInPunctuation: endsInPunctuation
});
}
export function getCompletionEntryDetails(query: Types.GetCompletionEntryDetailsQuery): Promise<Types.GetCompletionEntryDetailsResponse> {
const project = getProject(query.filePath);
const service = project.languageService;
const { filePath, position, label } = query;
const completionDetails = project.languageService.getCompletionEntryDetails(filePath, position, label, undefined, undefined);
/**
* For JS Projects, TS will add all sorts of globals as members (because it cannot know for sure)
* However if you try to `getCompletionEntryDetails` for them, you will get `undefined`.
*/
if (!completionDetails) return resolve({ display: label, comment: '' });
const comment = ts.displayPartsToString(completionDetails.documentation || []);
const display = ts.displayPartsToString(completionDetails.displayParts || []);
const result = { display: display, comment: comment };
return resolve(result);
}
export function quickInfo(query: Types.QuickInfoQuery): Promise<Types.QuickInfoResponse> {
let project = getProject(query.filePath);
const { languageServiceHost } = project;
const errors = positionErrors({ filePath: query.filePath, position: query.position });
var info = project.languageService.getQuickInfoAtPosition(query.filePath, query.position);
if (!info && !errors.length) {
return Promise.resolve({ valid: false });
} else {
return resolve({
valid: true,
info: info && {
name: ts.displayPartsToString(info.displayParts || []),
comment: ts.displayPartsToString(info.documentation || []),
range: {
from: project.languageServiceHost.getLineAndCharacterOfPosition(query.filePath, info.textSpan.start),
to: project.languageServiceHost.getLineAndCharacterOfPosition(query.filePath, info.textSpan.start + info.textSpan.length),
}
},
errors: errors
});
}
}
/** Utility */
function positionErrors(query: Types.FilePathPositionQuery): types.CodeError[] {
let project = getProject(query.filePath);
if (!project.includesSourceFile(query.filePath)) {
return [];
}
let editorPos = project.languageServiceHost.getLineAndCharacterOfPosition(query.filePath, query.position);
let errors = errorsCache.getErrorsForFilePath(query.filePath);
errors = errors.filter(e =>
// completely contained in the multiline
(e.from.line < editorPos.line && e.to.line > editorPos.line)
// error is single line and on the same line and characters match
|| (e.from.line == e.to.line && e.from.line == editorPos.line && e.from.ch <= editorPos.ch && e.to.ch >= editorPos.ch)
);
return errors;
}
export function getRenameInfo(query: Types.GetRenameInfoQuery): Promise<Types.GetRenameInfoResponse> {
let project = getProject(query.filePath);
var findInStrings = false, findInComments = false;
var info = project.languageService.getRenameInfo(query.filePath, query.position);
if (info && info.canRename) {
var locations: { [filePath: string]: ts.TextSpan[] } = {};
project.languageService.findRenameLocations(query.filePath, query.position, findInStrings, findInComments)
.forEach(loc => {
if (!locations[loc.fileName]) locations[loc.fileName] = [];
// Using unshift makes them with maximum value on top ;)
locations[loc.fileName].unshift(loc.textSpan);
});
return resolve({
canRename: true,
localizedErrorMessage: info.localizedErrorMessage,
displayName: info.displayName,
fullDisplayName: info.fullDisplayName,
kind: info.kind,
kindModifiers: info.kindModifiers,
triggerSpan: info.triggerSpan,
locations: locations
});
}
else {
return resolve({
canRename: false
});
}
}
export function getDefinitionsAtPosition(query: Types.GetDefinitionsAtPositionQuery): Promise<Types.GetDefinitionsAtPositionResponse> {
let project = getProject(query.filePath);
var definitions = project.languageService.getDefinitionAtPosition(query.filePath, query.position);
var projectFileDirectory = project.configFile.projectFileDirectory;
if (!definitions || !definitions.length) return resolve({ projectFileDirectory: projectFileDirectory, definitions: [] });
return resolve({
projectFileDirectory: projectFileDirectory,
definitions: definitions.map(d => {
// If we can get the filename *we are in the same program :P*
var pos = project.languageServiceHost.getLineAndCharacterOfPosition(d.fileName, d.textSpan.start);
return {
filePath: d.fileName,
position: pos,
span: d.textSpan,
};
})
});
}
import { getLangHelp } from "./modules/langHelp";
export function getDoctorInfo(query: Types.GetDoctorInfoQuery): Promise<Types.GetDoctorInfoResponse> {
let project = getProject(query.filePath);
let filePath = query.filePath;
let position = project.languageServiceHost.getPositionOfLineAndCharacter(query.filePath, query.editorPosition.line, query.editorPosition.ch);
// Get langHelp
const program = project.languageService.getProgram();
const sourceFile = program.getSourceFile(query.filePath);
const positionNode = ts.getTokenAtPosition(sourceFile, position, true);
const langHelp = getLangHelp(positionNode)
// Just collect other responses
let defPromised = getDefinitionsAtPosition({ filePath, position });
let quickInfoPromised = quickInfo({ filePath, position: position });
return defPromised.then((defRes) => {
return quickInfoPromised.then((infoRes) => {
return getReferences({ filePath, position }).then(refRes => {
const valid = !!defRes.definitions.length || infoRes.valid || !!refRes.references.length || !!langHelp;
return {
valid,
definitions: defRes.definitions,
quickInfo: infoRes.valid && infoRes.info.name ? {
name: infoRes.info.name,
comment: infoRes.info.comment
} : null,
langHelp,
references: refRes.references
}
});
});
});
}
export function getReferences(query: Types.GetReferencesQuery): Promise<Types.GetReferencesResponse> {
let project = getProject(query.filePath);
var languageService = project.languageService;
var references: ReferenceDetails[] = [];
var refs = languageService.getReferencesAtPosition(query.filePath, query.position) || [];
references = refs.map(r => {
const position = project.languageServiceHost.getLineAndCharacterOfPosition(r.fileName, r.textSpan.start);
return { filePath: r.fileName, position: position, span: r.textSpan }
});
return resolve({
references
})
}
/**
* Formatting
*/
import * as formatting from "./modules/formatting";
export function formatDocument(query: Types.FormatDocumentQuery): Promise<Types.FormatDocumentResponse> {
let project = getProject(query.filePath);
const { languageServiceHost, languageService } = project;
let tsresult = formatting.formatDocument(project, query.filePath, query.editorOptions);
const edits = tsresult.map(res => {
const result: Types.FormattingEdit = {
from: languageServiceHost.getLineAndCharacterOfPosition(query.filePath, res.span.start),
to: languageServiceHost.getLineAndCharacterOfPosition(query.filePath, res.span.start + res.span.length),
newText: res.newText
};
return result;
});
return resolve({ edits });
}
export function formatDocumentRange(query: Types.FormatDocumentRangeQuery): Promise<Types.FormatDocumentRangeResponse> {
let project = getProject(query.filePath);
const { languageServiceHost, languageService } = project;
let tsresult = formatting.formatDocumentRange(project, query.filePath, query.from, query.to, query.editorOptions);
const edits = tsresult.map(res => {
const result: Types.FormattingEdit = {
from: languageServiceHost.getLineAndCharacterOfPosition(query.filePath, res.span.start),
to: languageServiceHost.getLineAndCharacterOfPosition(query.filePath, res.span.start + res.span.length),
newText: res.newText
};
return result;
});
return resolve({ edits });
}
export function getFormattingEditsAfterKeystroke(query: Types.FormattingEditsAfterKeystrokeQuery): Promise<Types.FormattingEditsAfterKeystrokeResponse> {
let project = getProject(query.filePath);
const { languageServiceHost, languageService } = project;
const position = languageServiceHost.getPositionOfLineAndCharacter(query.filePath, query.editorPosition.line, query.editorPosition.ch);
const options = formatting.completeFormatCodeOptions(query.editorOptions, project.configFile.project.formatCodeOptions);
const tsresult = languageService.getFormattingEditsAfterKeystroke(query.filePath, position, query.key, options) || [];
const edits = tsresult.map(res => {
const result: Types.FormattingEdit = {
from: languageServiceHost.getLineAndCharacterOfPosition(query.filePath, res.span.start),
to: languageServiceHost.getLineAndCharacterOfPosition(query.filePath, res.span.start + res.span.length),
newText: res.newText
};
return result;
});
return resolve({ edits });
}
import { removeUnusedImports as removeUnusedImportsCore } from './modules/removeUnusedImports';
export function removeUnusedImports(query: Types.FilePathQuery): Promise<types.RefactoringsByFilePath> {
let project = getProject(query.filePath);
const { languageServiceHost, languageService } = project;
return resolve(removeUnusedImportsCore(query.filePath, languageService));
}
/**
* Symbol search
*/
//--------------------------------------------------------------------------
// getNavigateToItems
//--------------------------------------------------------------------------
// Look at
// https://github.com/Microsoft/TypeScript/blob/master/src/services/navigateTo.ts
// for inspiration
// Reason for forking:
// didn't give all results
// gave results from lib.d.ts
// I wanted the practice
function getSymbolsForFile(project: Project, sourceFile: ts.SourceFile): types.NavigateToItem[] {
let getNodeKind = ts.getNodeKind;
function getDeclarationName(declaration: ts.Declaration): string {
let result = ts.getNameOfDeclaration(declaration);
if (result === undefined) {
return '';
}
return result.getText();
}
function getTextOfIdentifierOrLiteral(node: ts.Node) {
if (node.kind === ts.SyntaxKind.Identifier ||
node.kind === ts.SyntaxKind.StringLiteral ||
node.kind === ts.SyntaxKind.NumericLiteral) {
return (<ts.Identifier | ts.LiteralExpression>node).text;
}
return undefined;
}
var items: types.NavigateToItem[] = [];
let declarations = sourceFile.getNamedDeclarations();
declarations.forEach((value: ts.Declaration[], key: string) => {
value.forEach(declaration => {
let item: types.NavigateToItem = {
name: getDeclarationName(declaration),
kind: getNodeKind(declaration),
filePath: sourceFile.fileName,
fileName: utils.getFileName(sourceFile.fileName),
position: project.languageServiceHost.getLineAndCharacterOfPosition(sourceFile.fileName, declaration.getStart())
}
items.push(item);
})
});
return items;
}
export function getNavigateToItems(query: {}): Promise<types.GetNavigateToItemsResponse> {
let project = activeProject.GetProject.getCurrentIfAny();
var languageService = project.languageService;
let items: types.NavigateToItem[] = [];
for (let file of project.getProjectSourceFiles()) {
getSymbolsForFile(project, file).forEach(i => items.push(i));
}
return utils.resolve({ items });
}
export function getNavigateToItemsForFilePath(query: { filePath: string }): Promise<types.GetNavigateToItemsResponse> {
let project = activeProject.GetProject.getCurrentIfAny();
var languageService = project.languageService;
const file = project.getSourceFile(query.filePath)
const items = getSymbolsForFile(project, file);
return utils.resolve({ items });
}
/**
* Dependency View
*/
import { getProgramDependencies } from "./modules/programDependencies";
export function getDependencies(query: {}): Promise<Types.GetDependenciesResponse> {
let project = activeProject.GetProject.getCurrentIfAny();
var links = getProgramDependencies(project.configFile, project.languageService.getProgram());
return resolve({ links });
}
/**
* AST View
*/
import { astToText, astToTextFull } from "./modules/astToText";
export function getAST(query: Types.GetASTQuery): Promise<Types.GetASTResponse> {
let project = getProject(query.filePath);
var service = project.languageService;
var files = service.getProgram().getSourceFiles().filter(x => x.fileName == query.filePath);
if (!files.length) resolve({});
var sourceFile = files[0];
let root = query.mode === Types.ASTMode.visitor
? astToText(sourceFile)
: astToTextFull(sourceFile);
return resolve({ root });
}
/**
* JS Ouput
*/
import { getRawJsOutput } from "./modules/building";
export function getJSOutputStatus(query: Types.FilePathQuery, autoEmit = true): types.GetJSOutputStatusResponse {
const project = activeProject.GetProject.ifCurrent(query.filePath);
if (!project) {
return {
inActiveProject: false
}
}
const jsFile = getRawJsOutput(project, query.filePath);
/**
* We just read/write from disk for now
* Would be better if it interacted with master
*/
const getContents = (filePath: string) => fsu.existsSync(filePath) ? fsu.readFile(filePath) : '';
const setContents = fsu.writeFile;
/**
* Note: If we have compileOnSave as false then the output status isn't relevant
*/
const noJsFile = (project.configFile.project.compileOnSave === false)
|| (project.configFile.inMemory === true)
|| !jsFile;
let state
= noJsFile
? types.JSOutputState.NoJSFile
: getContents(jsFile.filePath) === jsFile.contents
? types.JSOutputState.JSUpToDate
: types.JSOutputState.JSOutOfDate;
/**
* If the state is JSOutOfDate we can easily fix that to bring it up to date for `compileOnSave`
*/
if (autoEmit && state === types.JSOutputState.JSOutOfDate && project.configFile.project.compileOnSave !== false) {
setContents(jsFile.filePath, jsFile.contents);
state = types.JSOutputState.JSUpToDate;
}
const outputStatus: types.JSOutputStatus = {
inputFilePath: query.filePath,
state,
outputFilePath: jsFile && jsFile.filePath
};
return {
inActiveProject: true,
outputStatus
};
}
/**
* Get Quick Fix
*/
import { QuickFix, QuickFixQueryInformation } from "./quickFix/quickFix";
import * as qf from "./quickFix/quickFix";
import { allQuickFixes } from "./quickFix/quickFixRegistry";
function getDiagnositcsByFilePath(query: Types.FilePathQuery) {
let project = getProject(query.filePath);
var diagnostics = project.languageService.getSyntacticDiagnostics(query.filePath);
if (diagnostics.length === 0) {
diagnostics = project.languageService.getSemanticDiagnostics(query.filePath);
}
return diagnostics;
}
function getInfoForQuickFixAnalysis(query: Types.GetQuickFixesQuery): QuickFixQueryInformation {
let project = getProject(query.filePath);
let program = project.languageService.getProgram();
let sourceFile = program.getSourceFile(query.filePath);
let sourceFileText: string,
fileErrors: ts.Diagnostic[],
positionErrors: ts.Diagnostic[],
positionErrorMessages: string[],
positionNode: ts.Node;
if (project.includesSourceFile(query.filePath)) {
sourceFileText = sourceFile.getFullText();
fileErrors = getDiagnositcsByFilePath(query);
/** We want errors that are *touching* and thefore expand the query position by one */
positionErrors = fileErrors.filter(e => ((e.start - 1) < query.position) && (e.start + e.length + 1) > query.position);
positionErrorMessages = positionErrors.map(e => ts.flattenDiagnosticMessageText(e.messageText, '\n'));
positionNode = ts.getTokenAtPosition(sourceFile, query.position, true);
} else {
sourceFileText = "";
fileErrors = [];
positionErrors = [];
positionErrorMessages = [];
positionNode = undefined;
}
let service = project.languageService;
let typeChecker = program.getTypeChecker();
return {
project,
program,
sourceFile,
sourceFileText,
fileErrors,
positionErrors,
positionErrorMessages,
position: query.position,
positionNode,
service,
typeChecker,
filePath: query.filePath,
formatOptions: {
indentSize: query.indentSize
}
};
}
const tsCodefixPrefix = 'CodeFix:';
export function getQuickFixes(query: Types.GetQuickFixesQuery): Promise<Types.GetQuickFixesResponse> {
const project = getProject(query.filePath);
const info = getInfoForQuickFixAnalysis(query);
// We let the quickFix determine if it wants provide any fixes for this file
const fixes = allQuickFixes
.map(x => {
var canProvide = x.canProvideFix(info);
if (!canProvide)
return;
else
return { key: x.key, display: canProvide.display };
})
.filter(x => !!x);
/**
* TS Code fixes
* They comes with the `changes` on query. So we use that on `get` as well as `apply`
*/
const tsCodeFixes = project.languageService.getCodeFixesAtPosition(query.filePath, query.position, query.position, info.positionErrors.map(e => e.code), info.formatOptions);
if (tsCodeFixes.length) {
tsCodeFixes.forEach((fix, i) => {
fixes.unshift({
key: `${tsCodefixPrefix}${i}`, display: fix.description
})
})
}
return resolve({ fixes });
}
export function applyQuickFix(query: Types.ApplyQuickFixQuery): Promise<Types.ApplyQuickFixResponse> {
const info = getInfoForQuickFixAnalysis(query);
/**
* If TS Code fix
*/
if (query.key.startsWith(tsCodefixPrefix)) {
/** Find the code fix */
let project = getProject(query.filePath);
const tsCodeFixes = project.languageService.getCodeFixesAtPosition(query.filePath, query.position, query.position, info.positionErrors.map(e => e.code), info.formatOptions);
const index = +query.key.substr(tsCodefixPrefix.length);
const tsCodeFix = tsCodeFixes[index];
/** Map code fix to refactoring */
const refactorings: types.Refactoring[] = [];
tsCodeFix.changes.forEach(change => {
change.textChanges.forEach(tc => {
const res: types.Refactoring = {
filePath: change.fileName,
newText: tc.newText,
span: tc.span
};
refactorings.push(res);
});
});
return resolve({ refactorings: qf.getRefactoringsByFilePath(refactorings) });
}
const fix = allQuickFixes.filter(x => x.key == query.key)[0];
const res = fix.provideFix(info);
const refactorings = qf.getRefactoringsByFilePath(res);
return resolve({ refactorings });
}
/**
* Semantic Tree
*/
function sortNavbarItemsBySpan(items: ts.NavigationBarItem[]) {
items.sort((a, b) => a.spans[0].start - b.spans[0].start);
// sort children recursively
for (let item of items) {
if (item.childItems) {
sortNavbarItemsBySpan(item.childItems);
}
}
}
function flattenNavBarItems(items: ts.NavigationBarItem[]): ts.NavigationBarItem[] {
if (!items.length) return [];
const root = items[0];
/**
* Where we store the final good ones
*/
const results: ts.NavigationBarItem[] = [];
/** Just to remove the dupes for different keys */
const resultMapBig: { [key: string]: ts.NavigationBarItem } = Object.create(null);
/**
* The same items apprear with differnt indent, and different `spans`
* But at least one span seems to match, hence this key(s) function
*/
const getKeys = (item: ts.NavigationBarItem): string[] => {
return item.spans.map(span => {
return `${span.start}-${item.text}`
});
};
/** This is used to unflatten the resulting map */
const getParentMapKey = (item: ts.NavigationBarItem): string => {
return `${item.spans[0].start}-${item.text}`
};
const parentMap: { [key: string]: ts.NavigationBarItem } = {};
/**
* First create a map of everything
*/
const addToMap = (item: ts.NavigationBarItem, parent: ts.NavigationBarItem) => {
const keys = getKeys(item);
const previous = keys.some(key => !!resultMapBig[key]);
// If we already have it no need to add it.
// This is because the first time it gets added the parent then is the best one
if (!previous) {
keys.forEach(key => resultMapBig[key] = item);
results.push(item);
if (item !== root) {
const parentMapKey = getParentMapKey(item)
parentMap[parentMapKey] = parent;
}
}
// Whatever is in the final map is the version we want to use for parent pointers
const itemToUseAsParent = resultMapBig[getParentMapKey(item)];
// Also visit all children
item.childItems && item.childItems.forEach((child) => addToMap(child, itemToUseAsParent));
// Now delete the childItems as they are supposed to be restored by `parentMap`
delete item.childItems;
}
// Flatten into the map
items.forEach(item => addToMap(item, root));
// Now restore based on child pointers
results.forEach(item => {
if (item == root) return;
const key = getParentMapKey(item);
const parent = parentMap[key];
if (!parent.childItems) parent.childItems = [];
parent.childItems.push(item);
});
// Now we only need the children of the root :)
return results[0].childItems || [];
}
function navigationBarItemToSemanticTreeNode(item: ts.NavigationBarItem, project: Project, query: Types.FilePathQuery): Types.SemanticTreeNode {
let toReturn: Types.SemanticTreeNode = {
text: item.text,
kind: item.kind,
kindModifiers: item.kindModifiers,
start: project.languageServiceHost.getLineAndCharacterOfPosition(query.filePath, item.spans[0].start),
end: project.languageServiceHost.getLineAndCharacterOfPosition(query.filePath, item.spans[0].start + item.spans[0].length),
subNodes: item.childItems ? item.childItems.map(ci => navigationBarItemToSemanticTreeNode(ci, project, query)) : []
}
return toReturn;
}
export function getSemanticTree(query: Types.GetSemanticTreeQuery): Promise<Types.GetSemanticTreeReponse> {
let project = getProject(query.filePath);
let navBarItems = project.languageService.getNavigationBarItems(query.filePath);
// The nav bar from the language service has nodes at various levels (with duplication)
// We want a flat version
navBarItems = flattenNavBarItems(navBarItems);
// Sort items by first spans:
sortNavbarItemsBySpan(navBarItems);
// convert to SemanticTreeNodes
let nodes = navBarItems.map(nbi => navigationBarItemToSemanticTreeNode(nbi, project, query));
return resolve({ nodes });
}
/**
* Document highlights
*/
export function getOccurrencesAtPosition(query: Types.GetOccurancesAtPositionQuery): Promise<Types.GetOccurancesAtPositionResponse> {
let project = getProject(query.filePath);
const { languageServiceHost } = project;
const position = languageServiceHost.getPositionOfLineAndCharacter(query.filePath, query.editorPosition.line, query.editorPosition.ch);
const tsresults = project.languageService.getOccurrencesAtPosition(query.filePath, position) || [];
const results: Types.GetOccurancesAtPositionResult[] = tsresults.map(res => {
const result: Types.GetOccurancesAtPositionResult = {
filePath: res.fileName,
isWriteAccess: res.isWriteAccess,
start: project.languageServiceHost.getLineAndCharacterOfPosition(res.fileName, res.textSpan.start),
end: project.languageServiceHost.getLineAndCharacterOfPosition(res.fileName, res.textSpan.start + res.textSpan.length),
}
return result;
});
return resolve({ results });
}