Merge branch 'master' into builderApi

This commit is contained in:
Sheetal Nandi
2018-01-08 12:43:25 -08:00
594 changed files with 24753 additions and 6454 deletions

View File

@@ -46,7 +46,7 @@ function createCancellationToken(args: string[]): ServerCancellationToken {
let perRequestPipeName: string;
let currentRequestId: number;
return {
isCancellationRequested: () => perRequestPipeName !== undefined && pipeExists(perRequestPipeName),
isCancellationRequested: () => perRequestPipeName !== undefined && pipeExists(perRequestPipeName),
setRequest(requestId: number) {
currentRequestId = requestId;
perRequestPipeName = namePrefix + requestId;

View File

@@ -547,9 +547,11 @@ namespace ts.server {
}
switch (response.kind) {
case ActionSet:
project.resolutionCache.clear();
this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typeAcquisition, response.unresolvedImports, response.typings);
break;
case ActionInvalidate:
project.resolutionCache.clear();
this.typingsCache.deleteTypingsForProject(response.projectName);
break;
}
@@ -1774,9 +1776,10 @@ namespace ts.server {
const path = normalizedPathToPath(fileName, currentDirectory, this.toCanonicalFileName);
let info = this.getScriptInfoForPath(path);
if (!info) {
Debug.assert(isRootedDiskPath(fileName) || openedByClient, "Script info with relative file name can only be open script info");
Debug.assert(!isRootedDiskPath(fileName) || this.currentDirectory === currentDirectory || !this.openFilesWithNonRootedDiskPath.has(this.toCanonicalFileName(fileName)), "Open script files with non rooted disk path opened with current directory context cannot have same canonical names");
const isDynamic = isDynamicFileName(fileName);
Debug.assert(isRootedDiskPath(fileName) || isDynamic || openedByClient, "Script info with non-dynamic relative file name can only be open script info");
Debug.assert(!isRootedDiskPath(fileName) || this.currentDirectory === currentDirectory || !this.openFilesWithNonRootedDiskPath.has(this.toCanonicalFileName(fileName)), "Open script files with non rooted disk path opened with current directory context cannot have same canonical names");
Debug.assert(!isDynamic || this.currentDirectory === currentDirectory, "Dynamic files must always have current directory context since containing external project name will always match the script info name.");
// If the file is not opened by client and the file doesnot exist on the disk, return
if (!openedByClient && !isDynamic && !(hostToQueryFileExistsOn || this.host).fileExists(fileName)) {
return;

View File

@@ -121,6 +121,7 @@ namespace ts.server {
private program: Program;
private externalFiles: SortedReadonlyArray<string>;
private missingFilesMap: Map<FileWatcher>;
private plugins: PluginModule[] = [];
private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap();
private lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
@@ -516,7 +517,18 @@ namespace ts.server {
}
getExternalFiles(): SortedReadonlyArray<string> {
return emptyArray as SortedReadonlyArray<string>;
return toSortedArray(flatMap(this.plugins, plugin => {
if (typeof plugin.getExternalFiles !== "function") return;
try {
return plugin.getExternalFiles(this);
}
catch (e) {
this.projectService.logger.info(`A plugin threw an exception in getExternalFiles: ${e}`);
if (e.stack) {
this.projectService.logger.info(e.stack);
}
}
}));
}
getSourceFile(path: Path) {
@@ -1024,6 +1036,84 @@ namespace ts.server {
orderedRemoveItem(this.rootFiles, info);
this.rootFilesMap.delete(info.path);
}
protected enableGlobalPlugins() {
const host = this.projectService.host;
const options = this.getCompilationSettings();
if (!host.require) {
this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded");
return;
}
// Search our peer node_modules, then any globally-specified probe paths
// ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/
const searchPaths = [combinePaths(this.projectService.getExecutingFilePath(), "../../.."), ...this.projectService.pluginProbeLocations];
if (this.projectService.globalPlugins) {
// Enable global plugins with synthetic configuration entries
for (const globalPluginName of this.projectService.globalPlugins) {
// Skip empty names from odd commandline parses
if (!globalPluginName) continue;
// Skip already-locally-loaded plugins
if (options.plugins && options.plugins.some(p => p.name === globalPluginName)) continue;
// Provide global: true so plugins can detect why they can't find their config
this.projectService.logger.info(`Loading global plugin ${globalPluginName}`);
this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths);
}
}
}
protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]) {
this.projectService.logger.info(`Enabling plugin ${pluginConfigEntry.name} from candidate paths: ${searchPaths.join(",")}`);
const log = (message: string) => {
this.projectService.logger.info(message);
};
for (const searchPath of searchPaths) {
const resolvedModule = <PluginModuleFactory>Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log);
if (resolvedModule) {
this.enableProxy(resolvedModule, pluginConfigEntry);
return;
}
}
this.projectService.logger.info(`Couldn't find ${pluginConfigEntry.name}`);
}
private enableProxy(pluginModuleFactory: PluginModuleFactory, configEntry: PluginImport) {
try {
if (typeof pluginModuleFactory !== "function") {
this.projectService.logger.info(`Skipped loading plugin ${configEntry.name} because it did expose a proper factory function`);
return;
}
const info: PluginCreateInfo = {
config: configEntry,
project: this,
languageService: this.languageService,
languageServiceHost: this,
serverHost: this.projectService.host
};
const pluginModule = pluginModuleFactory({ typescript: ts });
const newLS = pluginModule.create(info);
for (const k of Object.keys(this.languageService)) {
if (!(k in newLS)) {
this.projectService.logger.info(`Plugin activation warning: Missing proxied method ${k} in created LS. Patching.`);
(newLS as any)[k] = (this.languageService as any)[k];
}
}
this.projectService.logger.info(`Plugin validation succeded`);
this.languageService = newLS;
this.plugins.push(pluginModule);
}
catch (e) {
this.projectService.logger.info(`Plugin activation failed: ${e}`);
}
}
}
/**
@@ -1087,6 +1177,7 @@ namespace ts.server {
projectService.host,
currentDirectory);
this.projectRootPath = projectRootPath && projectService.toCanonicalFileName(projectRootPath);
this.enableGlobalPlugins();
}
addRoot(info: ScriptInfo) {
@@ -1148,8 +1239,6 @@ namespace ts.server {
/*@internal*/
configFileSpecs: ConfigFileSpecs;
private plugins: PluginModule[] = [];
/** Ref count to the project when opened from external project */
private externalProjectRefCount = 0;
@@ -1231,69 +1320,7 @@ namespace ts.server {
}
}
if (this.projectService.globalPlugins) {
// Enable global plugins with synthetic configuration entries
for (const globalPluginName of this.projectService.globalPlugins) {
// Skip empty names from odd commandline parses
if (!globalPluginName) continue;
// Skip already-locally-loaded plugins
if (options.plugins && options.plugins.some(p => p.name === globalPluginName)) continue;
// Provide global: true so plugins can detect why they can't find their config
this.projectService.logger.info(`Loading global plugin ${globalPluginName}`);
this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths);
}
}
}
private enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]) {
this.projectService.logger.info(`Enabling plugin ${pluginConfigEntry.name} from candidate paths: ${searchPaths.join(",")}`);
const log = (message: string) => {
this.projectService.logger.info(message);
};
for (const searchPath of searchPaths) {
const resolvedModule = <PluginModuleFactory>Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log);
if (resolvedModule) {
this.enableProxy(resolvedModule, pluginConfigEntry);
return;
}
}
this.projectService.logger.info(`Couldn't find ${pluginConfigEntry.name}`);
}
private enableProxy(pluginModuleFactory: PluginModuleFactory, configEntry: PluginImport) {
try {
if (typeof pluginModuleFactory !== "function") {
this.projectService.logger.info(`Skipped loading plugin ${configEntry.name} because it did expose a proper factory function`);
return;
}
const info: PluginCreateInfo = {
config: configEntry,
project: this,
languageService: this.languageService,
languageServiceHost: this,
serverHost: this.projectService.host
};
const pluginModule = pluginModuleFactory({ typescript: ts });
const newLS = pluginModule.create(info);
for (const k of Object.keys(this.languageService)) {
if (!(k in newLS)) {
this.projectService.logger.info(`Plugin activation warning: Missing proxied method ${k} in created LS. Patching.`);
(newLS as any)[k] = (this.languageService as any)[k];
}
}
this.projectService.logger.info(`Plugin validation succeded`);
this.languageService = newLS;
this.plugins.push(pluginModule);
}
catch (e) {
this.projectService.logger.info(`Plugin activation failed: ${e}`);
}
this.enableGlobalPlugins();
}
/**
@@ -1322,21 +1349,6 @@ namespace ts.server {
return this.typeAcquisition;
}
getExternalFiles(): SortedReadonlyArray<string> {
return toSortedArray(flatMap(this.plugins, plugin => {
if (typeof plugin.getExternalFiles !== "function") return;
try {
return plugin.getExternalFiles(this);
}
catch (e) {
this.projectService.logger.info(`A plugin threw an exception in getExternalFiles: ${e}`);
if (e.stack) {
this.projectService.logger.info(e.stack);
}
}
}));
}
/*@internal*/
watchWildcards(wildcardDirectories: Map<WatchDirectoryFlags>) {
updateWatchingWildcardDirectories(

View File

@@ -566,7 +566,7 @@ namespace ts.server.protocol {
// TODO: GH#20538
/* @internal */
export interface GetCombinedCodeFixResponse extends Response {
export interface GetCombinedCodeFixResponse extends Response {
body: CombinedCodeActions;
}

View File

@@ -257,7 +257,7 @@ namespace ts.server {
export class ScriptVersionCache {
private changes: TextChange[] = [];
private readonly versions: LineIndexSnapshot[] = new Array<LineIndexSnapshot>(ScriptVersionCache.maxVersions);
private minVersion = 0; // no versions earlier than min version will maintain change history
private minVersion = 0; // no versions earlier than min version will maintain change history
private currentVersion = 0;

View File

@@ -979,6 +979,10 @@ namespace ts.server {
allowLocalPluginLoads
};
logger.info(`Starting TS Server`);
logger.info(`Version: ${versionMajorMinor}`);
logger.info(`Arguments: ${process.argv.join(" ")}`);
const ioSession = new IOSession(options);
process.on("uncaughtException", err => {
ioSession.logError(err, "unknown");

View File

@@ -1230,7 +1230,7 @@ namespace ts.server {
return project.getLanguageService().getCompletionEntryDetails(file, position, name, formattingOptions, source);
});
return simplifiedResult
? result.map(details => ({ ...details, codeActions: map(details.codeActions, action => this.mapCodeAction(action, scriptInfo)) }))
? result.map(details => ({ ...details, codeActions: map(details.codeActions, action => this.mapCodeAction(project, action)) }))
: result;
}
@@ -1560,7 +1560,7 @@ namespace ts.server {
return undefined;
}
if (simplifiedResult) {
return codeActions.map(codeAction => this.mapCodeAction(codeAction, scriptInfo));
return codeActions.map(codeAction => this.mapCodeAction(project, codeAction));
}
else {
return codeActions;
@@ -1613,8 +1613,8 @@ namespace ts.server {
return { startPosition, endPosition };
}
private mapCodeAction({ description, changes: unmappedChanges, commands }: CodeAction, scriptInfo: ScriptInfo): protocol.CodeAction {
const changes = unmappedChanges.map(change => this.mapTextChangesToCodeEditsUsingScriptinfo(change, scriptInfo));
private mapCodeAction(project: Project, { description, changes: unmappedChanges, commands }: CodeAction): protocol.CodeAction {
const changes = unmappedChanges.map(change => this.mapTextChangesToCodeEditsUsingScriptinfo(change, project.getScriptInfoForNormalizedPath(toNormalizedPath(change.fileName))));
return { description, changes, commands };
}