Merge branch 'master' of https://github.com/Microsoft/TypeScript into i4420

# Conflicts:
#	src/server/session.ts
This commit is contained in:
zhengbli
2016-04-21 15:17:46 -07:00
6 changed files with 237 additions and 127 deletions

View File

@@ -91,10 +91,10 @@ namespace ts {
return undefined;
}
export function contains<T>(array: T[], value: T): boolean {
export function contains<T>(array: T[], value: T, areEqual?: (a: T, b: T) => boolean): boolean {
if (array) {
for (const v of array) {
if (v === value) {
if (areEqual ? areEqual(v, value) : v === value) {
return true;
}
}
@@ -156,12 +156,12 @@ namespace ts {
return array1.concat(array2);
}
export function deduplicate<T>(array: T[]): T[] {
export function deduplicate<T>(array: T[], areEqual?: (a: T, b: T) => boolean): T[] {
let result: T[];
if (array) {
result = [];
for (const item of array) {
if (!contains(result, item)) {
if (!contains(result, item, areEqual)) {
result.push(item);
}
}

View File

@@ -498,6 +498,14 @@ namespace ts.server {
return copiedList;
}
/**
* This helper funciton processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project.
*/
export function combineProjectOutput<T>(projects: Project[], action: (project: Project) => T[], comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean) {
const result = projects.reduce<T[]>((previous, current) => concatenate(previous, action(current)), []).sort(comparer);
return projects.length > 1 ? deduplicate(result, areEqual) : result;
}
export interface ProjectServiceEventHandler {
(eventName: string, project: Project, fileName: string): void;
}

View File

@@ -141,8 +141,8 @@ namespace ts.server {
) {
this.projectService =
new ProjectService(host, logger, (eventName, project, fileName) => {
this.handleEvent(eventName, project, fileName);
});
this.handleEvent(eventName, project, fileName);
});
}
private handleEvent(eventName: string, project: Project, fileName: string) {
@@ -412,14 +412,17 @@ namespace ts.server {
private getRenameLocations(line: number, offset: number, fileName: string, findInComments: boolean, findInStrings: boolean): protocol.RenameResponseBody {
const file = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(file);
if (!project) {
const info = this.projectService.getScriptInfo(file);
const projects = this.projectService.findReferencingProjects(info);
if (!projects.length) {
throw Errors.NoProject;
}
const compilerService = project.compilerService;
const position = compilerService.host.lineOffsetToPosition(file, line, offset);
const renameInfo = compilerService.languageService.getRenameInfo(file, position);
const defaultProject = projects[0];
// The rename info should be the same for every project
const defaultProjectCompilerService = defaultProject.compilerService;
const position = defaultProjectCompilerService.host.lineOffsetToPosition(file, line, offset);
const renameInfo = defaultProjectCompilerService.languageService.getRenameInfo(file, position);
if (!renameInfo) {
return undefined;
}
@@ -431,16 +434,43 @@ namespace ts.server {
};
}
const renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments);
if (!renameLocations) {
return undefined;
}
const fileSpans = combineProjectOutput(
projects,
(project: Project) => {
const compilerService = project.compilerService;
const renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments);
if (!renameLocations) {
return [];
}
const bakedRenameLocs = renameLocations.map(location => (<protocol.FileSpan>{
file: location.fileName,
start: compilerService.host.positionToLineOffset(location.fileName, location.textSpan.start),
end: compilerService.host.positionToLineOffset(location.fileName, ts.textSpanEnd(location.textSpan)),
})).sort((a, b) => {
return renameLocations.map(location => (<protocol.FileSpan>{
file: location.fileName,
start: compilerService.host.positionToLineOffset(location.fileName, location.textSpan.start),
end: compilerService.host.positionToLineOffset(location.fileName, ts.textSpanEnd(location.textSpan)),
}));
},
compareRenameLocation,
(a, b) => a.file === b.file && a.start.line === b.start.line && a.start.offset === b.start.offset
);
const locs = fileSpans.reduce<protocol.SpanGroup[]>((accum, cur) => {
let curFileAccum: protocol.SpanGroup;
if (accum.length > 0) {
curFileAccum = accum[accum.length - 1];
if (curFileAccum.file !== cur.file) {
curFileAccum = undefined;
}
}
if (!curFileAccum) {
curFileAccum = { file: cur.file, locs: [] };
accum.push(curFileAccum);
}
curFileAccum.locs.push({ start: cur.start, end: cur.end });
return accum;
}, []);
return { info: renameInfo, locs };
function compareRenameLocation(a: protocol.FileSpan, b: protocol.FileSpan) {
if (a.file < b.file) {
return -1;
}
@@ -459,70 +489,70 @@ namespace ts.server {
return b.start.offset - a.start.offset;
}
}
}).reduce<protocol.SpanGroup[]>((accum: protocol.SpanGroup[], cur: protocol.FileSpan) => {
let curFileAccum: protocol.SpanGroup;
if (accum.length > 0) {
curFileAccum = accum[accum.length - 1];
if (curFileAccum.file != cur.file) {
curFileAccum = undefined;
}
}
if (!curFileAccum) {
curFileAccum = { file: cur.file, locs: [] };
accum.push(curFileAccum);
}
curFileAccum.locs.push({ start: cur.start, end: cur.end });
return accum;
}, []);
return { info: renameInfo, locs: bakedRenameLocs };
}
}
private getReferences(line: number, offset: number, fileName: string): protocol.ReferencesResponseBody {
// TODO: get all projects for this file; report refs for all projects deleting duplicates
// can avoid duplicates by eliminating same ref file from subsequent projects
const file = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(file);
if (!project) {
const info = this.projectService.getScriptInfo(file);
const projects = this.projectService.findReferencingProjects(info);
if (!projects.length) {
throw Errors.NoProject;
}
const compilerService = project.compilerService;
const position = compilerService.host.lineOffsetToPosition(file, line, offset);
const references = compilerService.languageService.getReferencesAtPosition(file, position);
if (!references) {
return undefined;
}
const nameInfo = compilerService.languageService.getQuickInfoAtPosition(file, position);
const defaultProject = projects[0];
const position = defaultProject.compilerService.host.lineOffsetToPosition(file, line, offset);
const nameInfo = defaultProject.compilerService.languageService.getQuickInfoAtPosition(file, position);
if (!nameInfo) {
return undefined;
}
const displayString = ts.displayPartsToString(nameInfo.displayParts);
const nameSpan = nameInfo.textSpan;
const nameColStart = compilerService.host.positionToLineOffset(file, nameSpan.start).offset;
const nameText = compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan));
const bakedRefs: protocol.ReferencesResponseItem[] = references.map(ref => {
const start = compilerService.host.positionToLineOffset(ref.fileName, ref.textSpan.start);
const refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1);
const snap = compilerService.host.getScriptSnapshot(ref.fileName);
const lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
return {
file: ref.fileName,
start: start,
lineText: lineText,
end: compilerService.host.positionToLineOffset(ref.fileName, ts.textSpanEnd(ref.textSpan)),
isWriteAccess: ref.isWriteAccess
};
}).sort(compareFileStart);
const nameColStart = defaultProject.compilerService.host.positionToLineOffset(file, nameSpan.start).offset;
const nameText = defaultProject.compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan));
const refs = combineProjectOutput<protocol.ReferencesResponseItem>(
projects,
(project: Project) => {
const compilerService = project.compilerService;
const references = compilerService.languageService.getReferencesAtPosition(file, position);
if (!references) {
return [];
}
return references.map(ref => {
const start = compilerService.host.positionToLineOffset(ref.fileName, ref.textSpan.start);
const refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1);
const snap = compilerService.host.getScriptSnapshot(ref.fileName);
const lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
return {
file: ref.fileName,
start: start,
lineText: lineText,
end: compilerService.host.positionToLineOffset(ref.fileName, ts.textSpanEnd(ref.textSpan)),
isWriteAccess: ref.isWriteAccess
};
});
},
compareFileStart,
areReferencesResponseItemsForTheSameLocation
);
return {
refs: bakedRefs,
refs,
symbolName: nameText,
symbolStartOffset: nameColStart,
symbolDisplayString: displayString
};
function areReferencesResponseItemsForTheSameLocation(a: protocol.ReferencesResponseItem, b: protocol.ReferencesResponseItem) {
if (a && b) {
return a.file === b.file &&
a.start === b.start &&
a.end === b.end;
}
return false;
}
}
/**
@@ -836,41 +866,60 @@ namespace ts.server {
private getNavigateToItems(searchValue: string, fileName: string, maxResultCount?: number): protocol.NavtoItem[] {
const file = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(file);
if (!project) {
const info = this.projectService.getScriptInfo(file);
const projects = this.projectService.findReferencingProjects(info);
const defaultProject = projects[0];
if (!defaultProject) {
throw Errors.NoProject;
}
const compilerService = project.compilerService;
const navItems = compilerService.languageService.getNavigateToItems(searchValue, maxResultCount);
if (!navItems) {
return undefined;
}
const allNavToItems = combineProjectOutput(
projects,
(project: Project) => {
const compilerService = project.compilerService;
const navItems = compilerService.languageService.getNavigateToItems(searchValue, maxResultCount);
if (!navItems) {
return [];
}
return navItems.map((navItem) => {
const start = compilerService.host.positionToLineOffset(navItem.fileName, navItem.textSpan.start);
const end = compilerService.host.positionToLineOffset(navItem.fileName, ts.textSpanEnd(navItem.textSpan));
const bakedItem: protocol.NavtoItem = {
name: navItem.name,
kind: navItem.kind,
file: navItem.fileName,
start: start,
end: end,
};
if (navItem.kindModifiers && (navItem.kindModifiers != "")) {
bakedItem.kindModifiers = navItem.kindModifiers;
return navItems.map((navItem) => {
const start = compilerService.host.positionToLineOffset(navItem.fileName, navItem.textSpan.start);
const end = compilerService.host.positionToLineOffset(navItem.fileName, ts.textSpanEnd(navItem.textSpan));
const bakedItem: protocol.NavtoItem = {
name: navItem.name,
kind: navItem.kind,
file: navItem.fileName,
start: start,
end: end,
};
if (navItem.kindModifiers && (navItem.kindModifiers !== "")) {
bakedItem.kindModifiers = navItem.kindModifiers;
}
if (navItem.matchKind !== "none") {
bakedItem.matchKind = navItem.matchKind;
}
if (navItem.containerName && (navItem.containerName.length > 0)) {
bakedItem.containerName = navItem.containerName;
}
if (navItem.containerKind && (navItem.containerKind.length > 0)) {
bakedItem.containerKind = navItem.containerKind;
}
return bakedItem;
});
},
/*comparer*/ undefined,
areNavToItemsForTheSameLocation
);
return allNavToItems;
function areNavToItemsForTheSameLocation(a: protocol.NavtoItem, b: protocol.NavtoItem) {
if (a && b) {
return a.file === b.file &&
a.start === b.start &&
a.end === b.end;
}
if (navItem.matchKind !== "none") {
bakedItem.matchKind = navItem.matchKind;
}
if (navItem.containerName && (navItem.containerName.length > 0)) {
bakedItem.containerName = navItem.containerName;
}
if (navItem.containerKind && (navItem.containerKind.length > 0)) {
bakedItem.containerKind = navItem.containerKind;
}
return bakedItem;
});
return false;
}
}
private getBraceMatching(line: number, offset: number, fileName: string): protocol.TextSpan[] {
@@ -944,26 +993,26 @@ namespace ts.server {
exit() {
}
private handlers: Map<(request: protocol.Request) => {response?: any, responseRequired?: boolean}> = {
private handlers: Map<(request: protocol.Request) => { response?: any, responseRequired?: boolean }> = {
[CommandNames.Exit]: () => {
this.exit();
return { responseRequired: false};
return { responseRequired: false };
},
[CommandNames.Definition]: (request: protocol.Request) => {
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
return {response: this.getDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true};
return { response: this.getDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };
},
[CommandNames.TypeDefinition]: (request: protocol.Request) => {
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
return {response: this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true};
return { response: this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };
},
[CommandNames.References]: (request: protocol.Request) => {
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
return {response: this.getReferences(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true};
return { response: this.getReferences(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };
},
[CommandNames.Rename]: (request: protocol.Request) => {
const renameArgs = <protocol.RenameRequestArgs>request.arguments;
return {response: this.getRenameLocations(renameArgs.line, renameArgs.offset, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings), responseRequired: true};
return { response: this.getRenameLocations(renameArgs.line, renameArgs.offset, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings), responseRequired: true };
},
[CommandNames.Open]: (request: protocol.Request) => {
const openArgs = <protocol.OpenRequestArgs>request.arguments;
@@ -983,52 +1032,54 @@ namespace ts.server {
break;
}
this.openClientFile(openArgs.file, openArgs.fileContent, scriptKind);
return {responseRequired: false};
return { responseRequired: false };
},
[CommandNames.Quickinfo]: (request: protocol.Request) => {
const quickinfoArgs = <protocol.FileLocationRequestArgs>request.arguments;
return {response: this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.offset, quickinfoArgs.file), responseRequired: true};
return { response: this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.offset, quickinfoArgs.file), responseRequired: true };
},
[CommandNames.Format]: (request: protocol.Request) => {
const formatArgs = <protocol.FormatRequestArgs>request.arguments;
return {response: this.getFormattingEditsForRange(formatArgs.line, formatArgs.offset, formatArgs.endLine, formatArgs.endOffset, formatArgs.file), responseRequired: true};
return { response: this.getFormattingEditsForRange(formatArgs.line, formatArgs.offset, formatArgs.endLine, formatArgs.endOffset, formatArgs.file), responseRequired: true };
},
[CommandNames.Formatonkey]: (request: protocol.Request) => {
const formatOnKeyArgs = <protocol.FormatOnKeyRequestArgs>request.arguments;
return {response: this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.offset, formatOnKeyArgs.key, formatOnKeyArgs.file), responseRequired: true};
return { response: this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.offset, formatOnKeyArgs.key, formatOnKeyArgs.file), responseRequired: true };
},
[CommandNames.Completions]: (request: protocol.Request) => {
const completionsArgs = <protocol.CompletionsRequestArgs>request.arguments;
return {response: this.getCompletions(completionsArgs.line, completionsArgs.offset, completionsArgs.prefix, completionsArgs.file), responseRequired: true};
return { response: this.getCompletions(completionsArgs.line, completionsArgs.offset, completionsArgs.prefix, completionsArgs.file), responseRequired: true };
},
[CommandNames.CompletionDetails]: (request: protocol.Request) => {
const completionDetailsArgs = <protocol.CompletionDetailsRequestArgs>request.arguments;
return {response: this.getCompletionEntryDetails(completionDetailsArgs.line, completionDetailsArgs.offset,
completionDetailsArgs.entryNames, completionDetailsArgs.file), responseRequired: true};
return {
response: this.getCompletionEntryDetails(completionDetailsArgs.line, completionDetailsArgs.offset,
completionDetailsArgs.entryNames, completionDetailsArgs.file), responseRequired: true
};
},
[CommandNames.SignatureHelp]: (request: protocol.Request) => {
const signatureHelpArgs = <protocol.SignatureHelpRequestArgs>request.arguments;
return {response: this.getSignatureHelpItems(signatureHelpArgs.line, signatureHelpArgs.offset, signatureHelpArgs.file), responseRequired: true};
return { response: this.getSignatureHelpItems(signatureHelpArgs.line, signatureHelpArgs.offset, signatureHelpArgs.file), responseRequired: true };
},
[CommandNames.Geterr]: (request: protocol.Request) => {
const geterrArgs = <protocol.GeterrRequestArgs>request.arguments;
return {response: this.getDiagnostics(geterrArgs.delay, geterrArgs.files), responseRequired: false};
return { response: this.getDiagnostics(geterrArgs.delay, geterrArgs.files), responseRequired: false };
},
[CommandNames.GeterrForProject]: (request: protocol.Request) => {
const { file, delay } = <protocol.GeterrForProjectRequestArgs>request.arguments;
return {response: this.getDiagnosticsForProject(delay, file), responseRequired: false};
return { response: this.getDiagnosticsForProject(delay, file), responseRequired: false };
},
[CommandNames.Change]: (request: protocol.Request) => {
const changeArgs = <protocol.ChangeRequestArgs>request.arguments;
this.change(changeArgs.line, changeArgs.offset, changeArgs.endLine, changeArgs.endOffset,
changeArgs.insertString, changeArgs.file);
return {responseRequired: false};
changeArgs.insertString, changeArgs.file);
return { responseRequired: false };
},
[CommandNames.Configure]: (request: protocol.Request) => {
const configureArgs = <protocol.ConfigureRequestArguments>request.arguments;
this.projectService.setHostConfiguration(configureArgs);
this.output(undefined, CommandNames.Configure, request.seq);
return {responseRequired: false};
return { responseRequired: false };
},
[CommandNames.Reload]: (request: protocol.Request) => {
const reloadArgs = <protocol.ReloadRequestArgs>request.arguments;
@@ -1038,50 +1089,50 @@ namespace ts.server {
[CommandNames.Saveto]: (request: protocol.Request) => {
const savetoArgs = <protocol.SavetoRequestArgs>request.arguments;
this.saveToTmp(savetoArgs.file, savetoArgs.tmpfile);
return {responseRequired: false};
return { responseRequired: false };
},
[CommandNames.Close]: (request: protocol.Request) => {
const closeArgs = <protocol.FileRequestArgs>request.arguments;
this.closeClientFile(closeArgs.file);
return {responseRequired: false};
return { responseRequired: false };
},
[CommandNames.Navto]: (request: protocol.Request) => {
const navtoArgs = <protocol.NavtoRequestArgs>request.arguments;
return {response: this.getNavigateToItems(navtoArgs.searchValue, navtoArgs.file, navtoArgs.maxResultCount), responseRequired: true};
return { response: this.getNavigateToItems(navtoArgs.searchValue, navtoArgs.file, navtoArgs.maxResultCount), responseRequired: true };
},
[CommandNames.Brace]: (request: protocol.Request) => {
const braceArguments = <protocol.FileLocationRequestArgs>request.arguments;
return {response: this.getBraceMatching(braceArguments.line, braceArguments.offset, braceArguments.file), responseRequired: true};
return { response: this.getBraceMatching(braceArguments.line, braceArguments.offset, braceArguments.file), responseRequired: true };
},
[CommandNames.NavBar]: (request: protocol.Request) => {
const navBarArgs = <protocol.FileRequestArgs>request.arguments;
return {response: this.getNavigationBarItems(navBarArgs.file), responseRequired: true};
return { response: this.getNavigationBarItems(navBarArgs.file), responseRequired: true };
},
[CommandNames.Occurrences]: (request: protocol.Request) => {
const { line, offset, file: fileName } = <protocol.FileLocationRequestArgs>request.arguments;
return {response: this.getOccurrences(line, offset, fileName), responseRequired: true};
return { response: this.getOccurrences(line, offset, fileName), responseRequired: true };
},
[CommandNames.DocumentHighlights]: (request: protocol.Request) => {
const { line, offset, file: fileName, filesToSearch } = <protocol.DocumentHighlightsRequestArgs>request.arguments;
return {response: this.getDocumentHighlights(line, offset, fileName, filesToSearch), responseRequired: true};
return { response: this.getDocumentHighlights(line, offset, fileName, filesToSearch), responseRequired: true };
},
[CommandNames.ProjectInfo]: (request: protocol.Request) => {
const { file, needFileNameList } = <protocol.ProjectInfoRequestArgs>request.arguments;
return {response: this.getProjectInfo(file, needFileNameList), responseRequired: true};
return { response: this.getProjectInfo(file, needFileNameList), responseRequired: true };
},
[CommandNames.ReloadProjects]: (request: protocol.ReloadProjectsRequest) => {
this.reloadProjects();
return {responseRequired: false};
return { responseRequired: false };
}
};
public addProtocolHandler(command: string, handler: (request: protocol.Request) => {response?: any, responseRequired: boolean}) {
public addProtocolHandler(command: string, handler: (request: protocol.Request) => { response?: any, responseRequired: boolean }) {
if (this.handlers[command]) {
throw new Error(`Protocol handler already exists for command "${command}"`);
}
this.handlers[command] = handler;
}
public executeCommand(request: protocol.Request): {response?: any, responseRequired?: boolean} {
public executeCommand(request: protocol.Request): { response?: any, responseRequired?: boolean } {
const handler = this.handlers[request.command];
if (handler) {
return handler(request);
@@ -1089,7 +1140,7 @@ namespace ts.server {
else {
this.projectService.log("Unrecognized JSON command: " + JSON.stringify(request));
this.output(undefined, CommandNames.Unknown, request.seq, "Unrecognized JSON command: " + request.command);
return {responseRequired: false};
return { responseRequired: false };
}
}

View File

@@ -0,0 +1,17 @@
/// <reference path="fourslash.ts" />
//@Filename: a.ts
////var /*1*/x: number;
//@Filename: b.ts
/////// <reference path="a.ts" />
////x++;
//@Filename: c.ts
/////// <reference path="a.ts" />
////x++;
goTo.file("a.ts");
goTo.marker("1");
verify.referencesCountIs(3);

View File

@@ -0,0 +1,17 @@
/// <reference path="fourslash.ts" />
//@Filename: a.ts
////var x: number;
//@Filename: b.ts
////var x: number;
//@Filename: c.ts
/////// <reference path="a.ts" />
/////// <reference path="b.ts" />
/////**/x++;
goTo.file("c.ts");
goTo.marker();
verify.definitionCountIs(2);

View File

@@ -0,0 +1,17 @@
/// <reference path="fourslash.ts" />
//@Filename: a.ts
////var /*1*/[|x|]: number;
//@Filename: b.ts
/////// <reference path="a.ts" />
////[|x|]++;
//@Filename: c.ts
/////// <reference path="a.ts" />
////[|x|]++;
goTo.file("a.ts");
goTo.marker("1");
verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false);