alm
Version:
The best IDE for TypeScript
681 lines (680 loc) • 29.4 kB
JavaScript
"use strict";
/**
* 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 :)
*/
Object.defineProperty(exports, "__esModule", { value: true });
var activeProject = require("./activeProject");
var getProject = activeProject.GetProject.ifCurrentOrErrorOut;
var socketContract_1 = require("../../../socket/socketContract");
var types = require("../../../common/types");
var utils = require("../../../common/utils");
var resolve = utils.resolve;
var fsu = require("../../utils/fsu");
var tsErrorsCache_1 = require("./cache/tsErrorsCache");
var getPathCompletions_1 = require("./modules/getPathCompletions");
function getCompletionsAtPosition(query) {
var filePath = query.filePath, position = query.position, prefix = query.prefix;
var project = getProject(query.filePath);
var service = project.languageService;
var languageServiceHost = project.languageServiceHost;
var completions = service.getCompletionsAtPosition(filePath, position, undefined);
var completionList = completions ? completions.entries.filter(function (x) { return !!x; }) : [];
var endsInPunctuation = utils.prefixEndsInPunctuation(prefix);
/** Doing too many suggestions is slowing us down in some cases */
var maxSuggestions = 10000;
// limit to maxSuggestions
if (completionList.length > maxSuggestions)
completionList = completionList.slice(0, maxSuggestions);
var completionsToReturn = completionList.map(function (c, index) {
return {
name: c.name,
kind: c.kind,
comment: '',
display: ''
};
});
/**
* Add function signature help
*/
if (query.prefix == '(') {
var signatures_1 = service.getSignatureHelpItems(query.filePath, query.position);
if (signatures_1 && signatures_1.items) {
signatures_1.items.forEach(function (item, index) {
var template = item.parameters.map(function (p, i) {
var display = '{{' + (i + 1) + ':' + ts.displayPartsToString(p.displayParts) + '}}';
return display;
}).join(ts.displayPartsToString(item.separatorDisplayParts));
var name = item.parameters.map(function (p) { return ts.displayPartsToString(p.displayParts); })
.join(ts.displayPartsToString(item.separatorDisplayParts));
// e.g. test(something:string):any;
// prefix: test(
// template: {{something}}
// suffix: ): any;
var description = 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_1.items.length
});
});
}
}
/**
* Add file path completions
*/
var pathCompletions = getPathCompletions_1.getPathCompletionsForAutocomplete({
position: position,
project: project,
filePath: filePath,
prefix: prefix
});
if (pathCompletions.length) {
completionsToReturn = pathCompletions.map(function (f) {
var result = {
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
});
}
exports.getCompletionsAtPosition = getCompletionsAtPosition;
function getCompletionEntryDetails(query) {
var project = getProject(query.filePath);
var service = project.languageService;
var filePath = query.filePath, position = query.position, label = query.label;
var 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: '' });
var comment = ts.displayPartsToString(completionDetails.documentation || []);
var display = ts.displayPartsToString(completionDetails.displayParts || []);
var result = { display: display, comment: comment };
return resolve(result);
}
exports.getCompletionEntryDetails = getCompletionEntryDetails;
function quickInfo(query) {
var project = getProject(query.filePath);
var languageServiceHost = project.languageServiceHost;
var 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
});
}
}
exports.quickInfo = quickInfo;
/** Utility */
function positionErrors(query) {
var project = getProject(query.filePath);
if (!project.includesSourceFile(query.filePath)) {
return [];
}
var editorPos = project.languageServiceHost.getLineAndCharacterOfPosition(query.filePath, query.position);
var errors = tsErrorsCache_1.errorsCache.getErrorsForFilePath(query.filePath);
errors = errors.filter(function (e) {
// completely contained in the multiline
return (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;
}
function getRenameInfo(query) {
var project = getProject(query.filePath);
var findInStrings = false, findInComments = false;
var info = project.languageService.getRenameInfo(query.filePath, query.position);
if (info && info.canRename) {
var locations = {};
project.languageService.findRenameLocations(query.filePath, query.position, findInStrings, findInComments)
.forEach(function (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
});
}
}
exports.getRenameInfo = getRenameInfo;
function getDefinitionsAtPosition(query) {
var 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(function (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,
};
})
});
}
exports.getDefinitionsAtPosition = getDefinitionsAtPosition;
var langHelp_1 = require("./modules/langHelp");
function getDoctorInfo(query) {
var project = getProject(query.filePath);
var filePath = query.filePath;
var position = project.languageServiceHost.getPositionOfLineAndCharacter(query.filePath, query.editorPosition.line, query.editorPosition.ch);
// Get langHelp
var program = project.languageService.getProgram();
var sourceFile = program.getSourceFile(query.filePath);
var positionNode = ts.getTokenAtPosition(sourceFile, position, true);
var langHelp = langHelp_1.getLangHelp(positionNode);
// Just collect other responses
var defPromised = getDefinitionsAtPosition({ filePath: filePath, position: position });
var quickInfoPromised = quickInfo({ filePath: filePath, position: position });
return defPromised.then(function (defRes) {
return quickInfoPromised.then(function (infoRes) {
return getReferences({ filePath: filePath, position: position }).then(function (refRes) {
var valid = !!defRes.definitions.length || infoRes.valid || !!refRes.references.length || !!langHelp;
return {
valid: valid,
definitions: defRes.definitions,
quickInfo: infoRes.valid && infoRes.info.name ? {
name: infoRes.info.name,
comment: infoRes.info.comment
} : null,
langHelp: langHelp,
references: refRes.references
};
});
});
});
}
exports.getDoctorInfo = getDoctorInfo;
function getReferences(query) {
var project = getProject(query.filePath);
var languageService = project.languageService;
var references = [];
var refs = languageService.getReferencesAtPosition(query.filePath, query.position) || [];
references = refs.map(function (r) {
var position = project.languageServiceHost.getLineAndCharacterOfPosition(r.fileName, r.textSpan.start);
return { filePath: r.fileName, position: position, span: r.textSpan };
});
return resolve({
references: references
});
}
exports.getReferences = getReferences;
/**
* Formatting
*/
var formatting = require("./modules/formatting");
function formatDocument(query) {
var project = getProject(query.filePath);
var languageServiceHost = project.languageServiceHost, languageService = project.languageService;
var tsresult = formatting.formatDocument(project, query.filePath, query.editorOptions);
var edits = tsresult.map(function (res) {
var result = {
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: edits });
}
exports.formatDocument = formatDocument;
function formatDocumentRange(query) {
var project = getProject(query.filePath);
var languageServiceHost = project.languageServiceHost, languageService = project.languageService;
var tsresult = formatting.formatDocumentRange(project, query.filePath, query.from, query.to, query.editorOptions);
var edits = tsresult.map(function (res) {
var result = {
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: edits });
}
exports.formatDocumentRange = formatDocumentRange;
function getFormattingEditsAfterKeystroke(query) {
var project = getProject(query.filePath);
var languageServiceHost = project.languageServiceHost, languageService = project.languageService;
var position = languageServiceHost.getPositionOfLineAndCharacter(query.filePath, query.editorPosition.line, query.editorPosition.ch);
var options = formatting.completeFormatCodeOptions(query.editorOptions, project.configFile.project.formatCodeOptions);
var tsresult = languageService.getFormattingEditsAfterKeystroke(query.filePath, position, query.key, options) || [];
var edits = tsresult.map(function (res) {
var result = {
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: edits });
}
exports.getFormattingEditsAfterKeystroke = getFormattingEditsAfterKeystroke;
var removeUnusedImports_1 = require("./modules/removeUnusedImports");
function removeUnusedImports(query) {
var project = getProject(query.filePath);
var languageServiceHost = project.languageServiceHost, languageService = project.languageService;
return resolve(removeUnusedImports_1.removeUnusedImports(query.filePath, languageService));
}
exports.removeUnusedImports = removeUnusedImports;
/**
* 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, sourceFile) {
var getNodeKind = ts.getNodeKind;
function getDeclarationName(declaration) {
var result = ts.getNameOfDeclaration(declaration);
if (result === undefined) {
return '';
}
return result.getText();
}
function getTextOfIdentifierOrLiteral(node) {
if (node.kind === ts.SyntaxKind.Identifier ||
node.kind === ts.SyntaxKind.StringLiteral ||
node.kind === ts.SyntaxKind.NumericLiteral) {
return node.text;
}
return undefined;
}
var items = [];
var declarations = sourceFile.getNamedDeclarations();
declarations.forEach(function (value, key) {
value.forEach(function (declaration) {
var item = {
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;
}
function getNavigateToItems(query) {
var project = activeProject.GetProject.getCurrentIfAny();
var languageService = project.languageService;
var items = [];
for (var _i = 0, _a = project.getProjectSourceFiles(); _i < _a.length; _i++) {
var file = _a[_i];
getSymbolsForFile(project, file).forEach(function (i) { return items.push(i); });
}
return utils.resolve({ items: items });
}
exports.getNavigateToItems = getNavigateToItems;
function getNavigateToItemsForFilePath(query) {
var project = activeProject.GetProject.getCurrentIfAny();
var languageService = project.languageService;
var file = project.getSourceFile(query.filePath);
var items = getSymbolsForFile(project, file);
return utils.resolve({ items: items });
}
exports.getNavigateToItemsForFilePath = getNavigateToItemsForFilePath;
/**
* Dependency View
*/
var programDependencies_1 = require("./modules/programDependencies");
function getDependencies(query) {
var project = activeProject.GetProject.getCurrentIfAny();
var links = programDependencies_1.getProgramDependencies(project.configFile, project.languageService.getProgram());
return resolve({ links: links });
}
exports.getDependencies = getDependencies;
/**
* AST View
*/
var astToText_1 = require("./modules/astToText");
function getAST(query) {
var project = getProject(query.filePath);
var service = project.languageService;
var files = service.getProgram().getSourceFiles().filter(function (x) { return x.fileName == query.filePath; });
if (!files.length)
resolve({});
var sourceFile = files[0];
var root = query.mode === socketContract_1.Types.ASTMode.visitor
? astToText_1.astToText(sourceFile)
: astToText_1.astToTextFull(sourceFile);
return resolve({ root: root });
}
exports.getAST = getAST;
/**
* JS Ouput
*/
var building_1 = require("./modules/building");
function getJSOutputStatus(query, autoEmit) {
if (autoEmit === void 0) { autoEmit = true; }
var project = activeProject.GetProject.ifCurrent(query.filePath);
if (!project) {
return {
inActiveProject: false
};
}
var jsFile = building_1.getRawJsOutput(project, query.filePath);
/**
* We just read/write from disk for now
* Would be better if it interacted with master
*/
var getContents = function (filePath) { return fsu.existsSync(filePath) ? fsu.readFile(filePath) : ''; };
var setContents = fsu.writeFile;
/**
* Note: If we have compileOnSave as false then the output status isn't relevant
*/
var noJsFile = (project.configFile.project.compileOnSave === false)
|| (project.configFile.inMemory === true)
|| !jsFile;
var 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;
}
var outputStatus = {
inputFilePath: query.filePath,
state: state,
outputFilePath: jsFile && jsFile.filePath
};
return {
inActiveProject: true,
outputStatus: outputStatus
};
}
exports.getJSOutputStatus = getJSOutputStatus;
var qf = require("./quickFix/quickFix");
var quickFixRegistry_1 = require("./quickFix/quickFixRegistry");
function getDiagnositcsByFilePath(query) {
var 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) {
var project = getProject(query.filePath);
var program = project.languageService.getProgram();
var sourceFile = program.getSourceFile(query.filePath);
var sourceFileText, fileErrors, positionErrors, positionErrorMessages, positionNode;
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(function (e) { return ((e.start - 1) < query.position) && (e.start + e.length + 1) > query.position; });
positionErrorMessages = positionErrors.map(function (e) { return ts.flattenDiagnosticMessageText(e.messageText, '\n'); });
positionNode = ts.getTokenAtPosition(sourceFile, query.position, true);
}
else {
sourceFileText = "";
fileErrors = [];
positionErrors = [];
positionErrorMessages = [];
positionNode = undefined;
}
var service = project.languageService;
var typeChecker = program.getTypeChecker();
return {
project: project,
program: program,
sourceFile: sourceFile,
sourceFileText: sourceFileText,
fileErrors: fileErrors,
positionErrors: positionErrors,
positionErrorMessages: positionErrorMessages,
position: query.position,
positionNode: positionNode,
service: service,
typeChecker: typeChecker,
filePath: query.filePath,
formatOptions: {
indentSize: query.indentSize
}
};
}
var tsCodefixPrefix = 'CodeFix:';
function getQuickFixes(query) {
var project = getProject(query.filePath);
var info = getInfoForQuickFixAnalysis(query);
// We let the quickFix determine if it wants provide any fixes for this file
var fixes = quickFixRegistry_1.allQuickFixes
.map(function (x) {
var canProvide = x.canProvideFix(info);
if (!canProvide)
return;
else
return { key: x.key, display: canProvide.display };
})
.filter(function (x) { return !!x; });
/**
* TS Code fixes
* They comes with the `changes` on query. So we use that on `get` as well as `apply`
*/
var tsCodeFixes = project.languageService.getCodeFixesAtPosition(query.filePath, query.position, query.position, info.positionErrors.map(function (e) { return e.code; }), info.formatOptions);
if (tsCodeFixes.length) {
tsCodeFixes.forEach(function (fix, i) {
fixes.unshift({
key: "" + tsCodefixPrefix + i, display: fix.description
});
});
}
return resolve({ fixes: fixes });
}
exports.getQuickFixes = getQuickFixes;
function applyQuickFix(query) {
var info = getInfoForQuickFixAnalysis(query);
/**
* If TS Code fix
*/
if (query.key.startsWith(tsCodefixPrefix)) {
/** Find the code fix */
var project = getProject(query.filePath);
var tsCodeFixes = project.languageService.getCodeFixesAtPosition(query.filePath, query.position, query.position, info.positionErrors.map(function (e) { return e.code; }), info.formatOptions);
var index = +query.key.substr(tsCodefixPrefix.length);
var tsCodeFix = tsCodeFixes[index];
/** Map code fix to refactoring */
var refactorings_1 = [];
tsCodeFix.changes.forEach(function (change) {
change.textChanges.forEach(function (tc) {
var res = {
filePath: change.fileName,
newText: tc.newText,
span: tc.span
};
refactorings_1.push(res);
});
});
return resolve({ refactorings: qf.getRefactoringsByFilePath(refactorings_1) });
}
var fix = quickFixRegistry_1.allQuickFixes.filter(function (x) { return x.key == query.key; })[0];
var res = fix.provideFix(info);
var refactorings = qf.getRefactoringsByFilePath(res);
return resolve({ refactorings: refactorings });
}
exports.applyQuickFix = applyQuickFix;
/**
* Semantic Tree
*/
function sortNavbarItemsBySpan(items) {
items.sort(function (a, b) { return a.spans[0].start - b.spans[0].start; });
// sort children recursively
for (var _i = 0, items_1 = items; _i < items_1.length; _i++) {
var item = items_1[_i];
if (item.childItems) {
sortNavbarItemsBySpan(item.childItems);
}
}
}
function flattenNavBarItems(items) {
if (!items.length)
return [];
var root = items[0];
/**
* Where we store the final good ones
*/
var results = [];
/** Just to remove the dupes for different keys */
var resultMapBig = 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
*/
var getKeys = function (item) {
return item.spans.map(function (span) {
return span.start + "-" + item.text;
});
};
/** This is used to unflatten the resulting map */
var getParentMapKey = function (item) {
return item.spans[0].start + "-" + item.text;
};
var parentMap = {};
/**
* First create a map of everything
*/
var addToMap = function (item, parent) {
var keys = getKeys(item);
var previous = keys.some(function (key) { return !!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(function (key) { return resultMapBig[key] = item; });
results.push(item);
if (item !== root) {
var parentMapKey = getParentMapKey(item);
parentMap[parentMapKey] = parent;
}
}
// Whatever is in the final map is the version we want to use for parent pointers
var itemToUseAsParent = resultMapBig[getParentMapKey(item)];
// Also visit all children
item.childItems && item.childItems.forEach(function (child) { return 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(function (item) { return addToMap(item, root); });
// Now restore based on child pointers
results.forEach(function (item) {
if (item == root)
return;
var key = getParentMapKey(item);
var 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, project, query) {
var toReturn = {
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(function (ci) { return navigationBarItemToSemanticTreeNode(ci, project, query); }) : []
};
return toReturn;
}
function getSemanticTree(query) {
var project = getProject(query.filePath);
var 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
var nodes = navBarItems.map(function (nbi) { return navigationBarItemToSemanticTreeNode(nbi, project, query); });
return resolve({ nodes: nodes });
}
exports.getSemanticTree = getSemanticTree;
/**
* Document highlights
*/
function getOccurrencesAtPosition(query) {
var project = getProject(query.filePath);
var languageServiceHost = project.languageServiceHost;
var position = languageServiceHost.getPositionOfLineAndCharacter(query.filePath, query.editorPosition.line, query.editorPosition.ch);
var tsresults = project.languageService.getOccurrencesAtPosition(query.filePath, position) || [];
var results = tsresults.map(function (res) {
var result = {
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: results });
}
exports.getOccurrencesAtPosition = getOccurrencesAtPosition;